diff --git a/docs/docsite/rst/porting_guides/porting_guide_2.5.rst b/docs/docsite/rst/porting_guides/porting_guide_2.5.rst index 3fdeaf633cb..f599f9b564e 100644 --- a/docs/docsite/rst/porting_guides/porting_guide_2.5.rst +++ b/docs/docsite/rst/porting_guides/porting_guide_2.5.rst @@ -196,12 +196,12 @@ Deprecation notices The following modules will be removed in Ansible 2.9. Please update your playbooks accordingly. * Apstra's ``aos_*`` modules are deprecated as they do not work with AOS 2.1 or higher. See new modules at `https://github.com/apstra `_. -* :ref:`nxos_ip_interface ` use :ref:`nxos_l3_interface ` instead. -* :ref:`nxos_portchannel ` use :ref:`nxos_linkagg ` instead. -* :ref:`nxos_switchport ` use :ref:`nxos_l2_interface ` instead. -* :ref:`panos_security_policy ` use :ref:`panos_security_rule ` instead. -* :ref:`panos_nat_policy ` use :ref:`panos_nat_rule ` instead. -* :ref:`vsphere_guest ` use :ref:`vmware_guest ` instead. +* nxos_ip_interface use :ref:`nxos_l3_interface ` instead. +* nxos_portchannel use :ref:`nxos_linkagg ` instead. +* nxos_switchport use :ref:`nxos_l2_interface ` instead. +* panos_security_policy use :ref:`panos_security_rule ` instead. +* panos_nat_policy use :ref:`panos_nat_rule ` instead. +* vsphere_guest use :ref:`vmware_guest ` instead. Noteworthy module changes ------------------------- diff --git a/docs/docsite/rst/porting_guides/porting_guide_2.9.rst b/docs/docsite/rst/porting_guides/porting_guide_2.9.rst index 4465689c8f7..5cf0b28292c 100644 --- a/docs/docsite/rst/porting_guides/porting_guide_2.9.rst +++ b/docs/docsite/rst/porting_guides/porting_guide_2.9.rst @@ -45,7 +45,16 @@ Modules removed The following modules no longer exist: -* No notable changes +* Apstra's ``aos_*`` modules. See the new modules at `https://github.com/apstra `_. +* ec2_ami_find +* kubernetes +* nxos_ip_interface use :ref:`nxos_l3_interface ` instead. +* nxos_portchannel use :ref:`nxos_linkagg ` instead. +* nxos_switchport use :ref:`nxos_l2_interface ` instead. +* oc +* panos_nat_policy use :ref:`panos_nat_rule ` instead. +* panos_security_policy use :ref:`panos_security_rule ` instead. +* vsphere_guest use :ref:`vmware_guest ` instead. Deprecation notices diff --git a/docs/docsite/rst/porting_guides/porting_guides.rst b/docs/docsite/rst/porting_guides/porting_guides.rst index 868a6ec6457..7f4ca34708d 100644 --- a/docs/docsite/rst/porting_guides/porting_guides.rst +++ b/docs/docsite/rst/porting_guides/porting_guides.rst @@ -18,3 +18,4 @@ Please note that this is not a complete list. If you believe any extra informati porting_guide_2.6 porting_guide_2.7 porting_guide_2.8 + porting_guide_2.9 diff --git a/lib/ansible/modules/cloud/amazon/_ec2_ami_find.py b/lib/ansible/modules/cloud/amazon/_ec2_ami_find.py index 58e8b45182b..4b173b2d117 100644 --- a/lib/ansible/modules/cloud/amazon/_ec2_ami_find.py +++ b/lib/ansible/modules/cloud/amazon/_ec2_ami_find.py @@ -7,417 +7,12 @@ __metaclass__ = type ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['deprecated'], + 'status': ['removed'], 'supported_by': 'community'} -DOCUMENTATION = r''' ---- -module: ec2_ami_find -version_added: '2.0' -short_description: Searches for AMIs to obtain the AMI ID and other information -deprecated: - removed_in: "2.9" - why: Various AWS modules have been combined and replaced with M(ec2_ami_facts). - alternative: Use M(ec2_ami_facts) instead. -description: - - Returns list of matching AMIs with AMI ID, along with other useful information - - Can search AMIs with different owners - - Can search by matching tag(s), by AMI name and/or other criteria - - Results can be sorted and sliced -author: "Tom Bamford (@tombamford)" -notes: - - This module is not backwards compatible with the previous version of the ec2_search_ami module which worked only for Ubuntu AMIs listed on - cloud-images.ubuntu.com. - - See the example below for a suggestion of how to search by distro/release. -options: - region: - description: - - The AWS region to use. - required: true - aliases: [ 'aws_region', 'ec2_region' ] - owner: - description: - - Search AMIs owned by the specified owner - - Can specify an AWS account ID, or one of the special IDs 'self', 'amazon' or 'aws-marketplace' - - If not specified, all EC2 AMIs in the specified region will be searched. - - You can include wildcards in many of the search options. An asterisk (*) matches zero or more characters, and a question mark (?) matches exactly one - character. You can escape special characters using a backslash (\) before the character. For example, a value of \*amazon\?\\ searches for the - literal string *amazon?\. - ami_id: - description: - - An AMI ID to match. - ami_tags: - description: - - A hash/dictionary of tags to match for the AMI. - architecture: - description: - - An architecture type to match (e.g. x86_64). - hypervisor: - description: - - A hypervisor type type to match (e.g. xen). - is_public: - description: - - Whether or not the image(s) are public. - type: bool - name: - description: - - An AMI name to match. - platform: - description: - - Platform type to match. - product_code: - description: - - Marketplace product code to match. - version_added: "2.3" - sort: - description: - - Optional attribute which with to sort the results. - - If specifying 'tag', the 'tag_name' parameter is required. - - Starting at version 2.1, additional sort choices of architecture, block_device_mapping, creationDate, hypervisor, is_public, location, owner_id, - platform, root_device_name, root_device_type, state, and virtualization_type are supported. - choices: - - 'name' - - 'description' - - 'tag' - - 'architecture' - - 'block_device_mapping' - - 'creationDate' - - 'hypervisor' - - 'is_public' - - 'location' - - 'owner_id' - - 'platform' - - 'root_device_name' - - 'root_device_type' - - 'state' - - 'virtualization_type' - sort_tag: - description: - - Tag name with which to sort results. - - Required when specifying 'sort=tag'. - sort_order: - description: - - Order in which to sort results. - - Only used when the 'sort' parameter is specified. - choices: ['ascending', 'descending'] - default: 'ascending' - sort_start: - description: - - Which result to start with (when sorting). - - Corresponds to Python slice notation. - sort_end: - description: - - Which result to end with (when sorting). - - Corresponds to Python slice notation. - state: - description: - - AMI state to match. - default: 'available' - virtualization_type: - description: - - Virtualization type to match (e.g. hvm). - root_device_type: - description: - - Root device type to match (e.g. ebs, instance-store). - version_added: "2.5" - no_result_action: - description: - - What to do when no results are found. - - "'success' reports success and returns an empty array" - - "'fail' causes the module to report failure" - choices: ['success', 'fail'] - default: 'success' -extends_documentation_fragment: - - aws -requirements: - - "python >= 2.6" - - boto - -''' - -EXAMPLES = ''' -# Note: These examples do not set authentication details, see the AWS Guide for details. - -# Search for the AMI tagged "project:website" -- ec2_ami_find: - owner: self - ami_tags: - project: website - no_result_action: fail - register: ami_find - -# Search for the latest Ubuntu 14.04 AMI -- ec2_ami_find: - name: "ubuntu/images/ebs/ubuntu-trusty-14.04-amd64-server-*" - owner: 099720109477 - sort: name - sort_order: descending - sort_end: 1 - register: ami_find - -# Launch an EC2 instance -- ec2: - image: "{{ ami_find.results[0].ami_id }}" - instance_type: m3.medium - key_name: mykey - wait: yes -''' - -RETURN = ''' -ami_id: - description: id of found amazon image - returned: when AMI found - type: str - sample: "ami-e9095e8c" -architecture: - description: architecture of image - returned: when AMI found - type: str - sample: "x86_64" -block_device_mapping: - description: block device mapping associated with image - returned: when AMI found - type: dict - sample: "{ - '/dev/xvda': { - 'delete_on_termination': true, - 'encrypted': false, - 'size': 8, - 'snapshot_id': 'snap-ca0330b8', - 'volume_type': 'gp2' - }" -creationDate: - description: creation date of image - returned: when AMI found - type: str - sample: "2015-10-15T22:43:44.000Z" -description: - description: description of image - returned: when AMI found - type: str - sample: "test-server01" -hypervisor: - description: type of hypervisor - returned: when AMI found - type: str - sample: "xen" -is_public: - description: whether image is public - returned: when AMI found - type: bool - sample: false -location: - description: location of image - returned: when AMI found - type: str - sample: "435210894375/test-server01-20151015-234343" -name: - description: ami name of image - returned: when AMI found - type: str - sample: "test-server01-20151015-234343" -owner_id: - description: owner of image - returned: when AMI found - type: str - sample: "435210894375" -platform: - description: platform of image - returned: when AMI found - type: str - sample: null -root_device_name: - description: root device name of image - returned: when AMI found - type: str - sample: "/dev/xvda" -root_device_type: - description: root device type of image - returned: when AMI found - type: str - sample: "ebs" -state: - description: state of image - returned: when AMI found - type: str - sample: "available" -tags: - description: tags assigned to image - returned: when AMI found - type: dict - sample: "{ - 'Environment': 'devel', - 'Name': 'test-server01', - 'Role': 'web' - }" -virtualization_type: - description: image virtualization type - returned: when AMI found - type: str - sample: "hvm" -''' - -import json - -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.ec2 import HAS_BOTO, ec2_argument_spec, ec2_connect - - -def get_block_device_mapping(image): - """ - Retrieves block device mapping from AMI - """ - - bdm_dict = dict() - bdm = getattr(image, 'block_device_mapping') - for device_name in bdm.keys(): - bdm_dict[device_name] = { - 'size': bdm[device_name].size, - 'snapshot_id': bdm[device_name].snapshot_id, - 'volume_type': bdm[device_name].volume_type, - 'encrypted': bdm[device_name].encrypted, - 'delete_on_termination': bdm[device_name].delete_on_termination - } - - return bdm_dict - - -def main(): - argument_spec = ec2_argument_spec() - argument_spec.update(dict( - owner=dict(required=False, default=None), - ami_id=dict(required=False), - ami_tags=dict(required=False, type='dict', - aliases=['search_tags', 'image_tags']), - architecture=dict(required=False), - hypervisor=dict(required=False), - is_public=dict(required=False, type='bool'), - name=dict(required=False), - platform=dict(required=False), - product_code=dict(required=False), - sort=dict(required=False, default=None, - choices=['name', 'description', 'tag', 'architecture', 'block_device_mapping', 'creationDate', 'hypervisor', 'is_public', 'location', - 'owner_id', 'platform', 'root_device_name', 'root_device_type', 'state', 'virtualization_type']), - sort_tag=dict(required=False), - sort_order=dict(required=False, default='ascending', - choices=['ascending', 'descending']), - sort_start=dict(required=False), - sort_end=dict(required=False), - state=dict(required=False, default='available'), - virtualization_type=dict(required=False), - no_result_action=dict(required=False, default='success', - choices=['success', 'fail']), - ) - ) - - module = AnsibleModule( - argument_spec=argument_spec, - supports_check_mode=True, - ) - - module.deprecate("The 'ec2_ami_find' module has been deprecated. Use 'ec2_ami_facts' instead.", version=2.9) - - if not HAS_BOTO: - module.fail_json(msg='boto required for this module, install via pip or your package manager') - - ami_id = module.params.get('ami_id') - ami_tags = module.params.get('ami_tags') - architecture = module.params.get('architecture') - hypervisor = module.params.get('hypervisor') - is_public = module.params.get('is_public') - name = module.params.get('name') - owner = module.params.get('owner') - platform = module.params.get('platform') - product_code = module.params.get('product_code') - root_device_type = module.params.get('root_device_type') - sort = module.params.get('sort') - sort_tag = module.params.get('sort_tag') - sort_order = module.params.get('sort_order') - sort_start = module.params.get('sort_start') - sort_end = module.params.get('sort_end') - state = module.params.get('state') - virtualization_type = module.params.get('virtualization_type') - no_result_action = module.params.get('no_result_action') - - filter = {'state': state} - - if ami_id: - filter['image_id'] = ami_id - if ami_tags: - for tag in ami_tags: - filter['tag:' + tag] = ami_tags[tag] - if architecture: - filter['architecture'] = architecture - if hypervisor: - filter['hypervisor'] = hypervisor - if is_public: - filter['is_public'] = 'true' - if name: - filter['name'] = name - if platform: - filter['platform'] = platform - if product_code: - filter['product-code'] = product_code - if root_device_type: - filter['root_device_type'] = root_device_type - if virtualization_type: - filter['virtualization_type'] = virtualization_type - - ec2 = ec2_connect(module) - - images_result = ec2.get_all_images(owners=owner, filters=filter) - - if no_result_action == 'fail' and len(images_result) == 0: - module.fail_json(msg="No AMIs matched the attributes: %s" % json.dumps(filter)) - - results = [] - for image in images_result: - data = { - 'ami_id': image.id, - 'architecture': image.architecture, - 'block_device_mapping': get_block_device_mapping(image), - 'creationDate': image.creationDate, - 'description': image.description, - 'hypervisor': image.hypervisor, - 'is_public': image.is_public, - 'location': image.location, - 'name': image.name, - 'owner_id': image.owner_id, - 'platform': image.platform, - 'root_device_name': image.root_device_name, - 'root_device_type': image.root_device_type, - 'state': image.state, - 'tags': image.tags, - 'virtualization_type': image.virtualization_type, - } - - if image.kernel_id: - data['kernel_id'] = image.kernel_id - if image.ramdisk_id: - data['ramdisk_id'] = image.ramdisk_id - - results.append(data) - - if sort == 'tag': - if not sort_tag: - module.fail_json(msg="'sort_tag' option must be given with 'sort=tag'") - results.sort(key=lambda e: e['tags'][sort_tag], reverse=(sort_order == 'descending')) - elif sort: - results.sort(key=lambda e: e[sort], reverse=(sort_order == 'descending')) - - try: - if sort and sort_start and sort_end: - results = results[int(sort_start):int(sort_end)] - elif sort and sort_start: - results = results[int(sort_start):] - elif sort and sort_end: - results = results[:int(sort_end)] - except TypeError: - module.fail_json(msg="Please supply numeric values for sort_start and/or sort_end") - - module.exit_json(results=results) +from ansible.module_utils.common.removed import removed_module if __name__ == '__main__': - main() + removed_module(removed_in='2.9') diff --git a/lib/ansible/modules/cloud/vmware/_vsphere_guest.py b/lib/ansible/modules/cloud/vmware/_vsphere_guest.py index ff23e6daef5..8784570a662 100644 --- a/lib/ansible/modules/cloud/vmware/_vsphere_guest.py +++ b/lib/ansible/modules/cloud/vmware/_vsphere_guest.py @@ -8,1935 +8,12 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['deprecated'], + 'status': ['removed'], 'supported_by': 'community'} -DOCUMENTATION = ''' ---- -module: vsphere_guest -short_description: Create/delete/manage a guest VM through VMware vSphere. -deprecated: - removed_in: "2.9" - why: "Replaced by M(vmware_guest) module. Also, 'Pysphere' is deprecated in favor of VMware's Official Python bindings - 'Pyvmomi'." - alternative: Use M(vmware_guest) and other vmware guest related modules instead. -description: - - Create/delete/reconfigure a guest VM through VMware vSphere. This module has a dependency on pysphere >= 1.7 -version_added: "1.6" -options: - vcenter_hostname: - description: - - The hostname of the vcenter server the module will connect to, to create the guest. - required: true - validate_certs: - description: - - Validate SSL certs. Note, if running on python without SSLContext - support (typically, python < 2.7.9) you will have to set this to C(no) - as pysphere does not support validating certificates on older python. - Prior to 2.1, this module would always validate on python >= 2.7.9 and - never validate on python <= 2.7.8. - type: bool - default: 'yes' - version_added: 2.1 - guest: - description: - - The virtual server name you wish to manage. - required: true - username: - description: - - Username to connect to vcenter as. - required: true - password: - description: - - Password of the user to connect to vcenter as. - required: true - resource_pool: - description: - - The name of the resource_pool to create the VM in. - cluster: - description: - - The name of the cluster to create the VM in. By default this is derived from the host you tell the module to build the guest on. - esxi: - description: - - Dictionary which includes datacenter and hostname on which the VM should be created. For standalone ESXi hosts, ha-datacenter should be used as the - datacenter name - state: - description: - - Indicate desired state of the vm. 'reconfigured' only applies changes to 'vm_cdrom', 'memory_mb', and 'num_cpus' in vm_hardware parameter. - The 'memory_mb' and 'num_cpus' changes are applied to powered-on vms when hot-plugging is enabled for the guest. - default: present - choices: ['present', 'powered_off', 'absent', 'powered_on', 'restarted', 'reconfigured', 'reboot_guest', 'poweroff_guest'] - from_template: - version_added: "1.9" - description: - - Specifies if the VM should be deployed from a template (mutually exclusive with 'state' parameter). No guest customization changes to hardware - such as CPU, RAM, NICs or Disks can be applied when launching from template. - type: bool - default: 'no' - template_src: - version_added: "1.9" - description: - - Name of the source template to deploy from - snapshot_to_clone: - description: - - A string that when specified, will create a linked clone copy of the VM. Snapshot must already be taken in vCenter. - version_added: "2.0" - power_on_after_clone: - description: - - Specifies if the VM should be powered on after the clone. - type: bool - default: 'yes' - vm_disk: - description: - - A key, value list of disks and their sizes and which datastore to keep it in. - vm_hardware: - description: - - A key, value list of VM config settings. Must include ['memory_mb', 'num_cpus', 'osid', 'scsi']. - vm_nic: - description: - - A key, value list of nics, their types and what network to put them on. - - Optionaly with their MAC address. - vm_extra_config: - description: - - A key, value pair of any extra values you want set or changed in the vmx file of the VM. Useful to set advanced options on the VM. - vm_hw_version: - description: - - Desired hardware version identifier (for example, "vmx-08" for vms that needs to be managed with vSphere Client). Note that changing hardware - version of existing vm is not supported. - version_added: "1.7" - vmware_guest_facts: - description: - - Gather facts from vCenter on a particular VM - type: bool - force: - description: - - Boolean. Allows you to run commands which may alter the running state of a guest. Also used to reconfigure and destroy. - type: bool - default: 'no' -notes: - - This module should run from a system that can access vSphere directly. - Either by using local_action, or using delegate_to. -author: -- Richard Hoop (@rhoop) -requirements: - - "python >= 2.6" - - pysphere -''' - - -EXAMPLES = ''' ---- -# Create a new VM on an ESX server -# Returns changed = False when the VM already exists -# Returns changed = True and a adds ansible_facts from the new VM -# State will set the power status of a guest upon creation. Use powered_on to create and boot. -# Options ['state', 'vm_extra_config', 'vm_disk', 'vm_nic', 'vm_hardware', 'esxi'] are required together -# Note: vm_floppy support added in 2.0 - -- vsphere_guest: - vcenter_hostname: vcenter.mydomain.local - username: myuser - password: mypass - guest: newvm001 - state: powered_on - vm_extra_config: - vcpu.hotadd: yes - mem.hotadd: yes - notes: This is a test VM - folder: MyFolder - vm_disk: - disk1: - size_gb: 10 - type: thin - datastore: storage001 - # VMs can be put into folders. The value given here is either the full path - # to the folder (e.g. production/customerA/lamp) or just the last component - # of the path (e.g. lamp): - folder: production/customerA/lamp - vm_nic: - nic1: - type: vmxnet3 - network: VM Network - network_type: standard - nic2: - type: vmxnet3 - network: dvSwitch Network - network_type: dvs - vm_hardware: - memory_mb: 2048 - num_cpus: 2 - osid: centos64Guest - scsi: paravirtual - vm_cdrom: - type: "iso" - iso_path: "DatastoreName/cd-image.iso" - vm_floppy: - type: "image" - image_path: "DatastoreName/floppy-image.flp" - esxi: - datacenter: MyDatacenter - hostname: esx001.mydomain.local - delegate_to: localhost - -# Reconfigure the CPU and Memory on the newly created VM -# Will return the changes made - -- vsphere_guest: - vcenter_hostname: vcenter.mydomain.local - username: myuser - password: mypass - guest: newvm001 - state: reconfigured - vm_extra_config: - vcpu.hotadd: yes - mem.hotadd: yes - notes: This is a test VM - vm_disk: - disk1: - size_gb: 10 - type: thin - datastore: storage001 - vm_nic: - nic1: - type: vmxnet3 - network: VM Network - network_type: standard - vm_hardware: - memory_mb: 4096 - num_cpus: 4 - osid: centos64Guest - scsi: paravirtual - esxi: - datacenter: MyDatacenter - hostname: esx001.mydomain.local - delegate_to: localhost - -# Deploy a guest from a template -- vsphere_guest: - vcenter_hostname: vcenter.mydomain.local - username: myuser - password: mypass - guest: newvm001 - from_template: yes - template_src: centosTemplate - cluster: MainCluster - resource_pool: "/Resources" - vm_extra_config: - folder: MyFolder - delegate_to: localhost - -# Task to gather facts from a vSphere cluster only if the system is a VMware guest -- vsphere_guest: - vcenter_hostname: vcenter.mydomain.local - username: myuser - password: mypass - guest: newvm001 - vmware_guest_facts: yes - delegate_to: localhost - ---- -# Typical output of a vsphere_facts run on a guest -# If vmware tools is not installed, ipaddresses with return None - -- hw_eth0: - - addresstype: "assigned" - label: "Network adapter 1" - macaddress: "00:22:33:33:44:55" - macaddress_dash: "00-22-33-33-44-55" - ipaddresses: ['192.0.2.100', '2001:DB8:56ff:feac:4d8a'] - summary: "VM Network" - hw_guest_full_name: "newvm001" - hw_guest_id: "rhel6_64Guest" - hw_memtotal_mb: 2048 - hw_name: "centos64Guest" - hw_power_status: "POWERED ON" - hw_processor_count: 2 - hw_product_uuid: "ef50bac8-2845-40ff-81d9-675315501dac" - -# hw_power_status will be one of the following values: -# - POWERED ON -# - POWERED OFF -# - SUSPENDED -# - POWERING ON -# - POWERING OFF -# - SUSPENDING -# - RESETTING -# - BLOCKED ON MSG -# - REVERTING TO SNAPSHOT -# - UNKNOWN -# as seen in the VMPowerState-Class of PySphere: http://git.io/vlwOq - ---- -# Remove a vm from vSphere -# The VM must be powered_off or you need to use force to force a shutdown -- vsphere_guest: - vcenter_hostname: vcenter.mydomain.local - username: myuser - password: mypass - guest: newvm001 - state: absent - force: yes - delegate_to: localhost -''' - -import os -import re -import ssl -import socket -import traceback - -HAS_PYSPHERE = False -try: - from pysphere import VIServer, VIProperty, MORTypes - from pysphere.resources import VimService_services as VI - from pysphere.vi_task import VITask - from pysphere import VIApiException - HAS_PYSPHERE = True -except ImportError: - pass - -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.six import string_types -from ansible.module_utils._text import to_native - - -# TODO: -# Ability to set CPU/Memory reservations - -def add_scsi_controller(module, s, config, devices, type="paravirtual", bus_num=0, disk_ctrl_key=1): - # add a scsi controller - scsi_ctrl_spec = config.new_deviceChange() - scsi_ctrl_spec.set_element_operation('add') - - if type == "lsi": - # For RHEL5 - scsi_ctrl = VI.ns0.VirtualLsiLogicController_Def("scsi_ctrl").pyclass() - elif type == "paravirtual": - # For RHEL6 - scsi_ctrl = VI.ns0.ParaVirtualSCSIController_Def("scsi_ctrl").pyclass() - elif type == "lsi_sas": - scsi_ctrl = VI.ns0.VirtualLsiLogicSASController_Def( - "scsi_ctrl").pyclass() - elif type == "bus_logic": - scsi_ctrl = VI.ns0.VirtualBusLogicController_Def("scsi_ctrl").pyclass() - else: - s.disconnect() - module.fail_json( - msg="Error adding scsi controller to vm spec. No scsi controller" - " type of: %s" % (type)) - - scsi_ctrl.set_element_busNumber(int(bus_num)) - scsi_ctrl.set_element_key(int(disk_ctrl_key)) - scsi_ctrl.set_element_sharedBus("noSharing") - scsi_ctrl_spec.set_element_device(scsi_ctrl) - # Add the scsi controller to the VM spec. - devices.append(scsi_ctrl_spec) - return disk_ctrl_key - - -def add_disk(module, s, config_target, config, devices, datastore, type="thin", size=200000, disk_ctrl_key=1, disk_number=0, key=0): - # add a vmdk disk - # Verify the datastore exists - datastore_name, ds = find_datastore(module, s, datastore, config_target) - # create a new disk - file based - for the vm - disk_spec = config.new_deviceChange() - disk_spec.set_element_fileOperation("create") - disk_spec.set_element_operation("add") - disk_ctlr = VI.ns0.VirtualDisk_Def("disk_ctlr").pyclass() - disk_backing = VI.ns0.VirtualDiskFlatVer2BackingInfo_Def( - "disk_backing").pyclass() - disk_backing.set_element_fileName(datastore_name) - disk_backing.set_element_diskMode("persistent") - if type != "thick": - disk_backing.set_element_thinProvisioned(1) - disk_ctlr.set_element_key(key) - disk_ctlr.set_element_controllerKey(int(disk_ctrl_key)) - disk_ctlr.set_element_unitNumber(int(disk_number)) - disk_ctlr.set_element_backing(disk_backing) - disk_ctlr.set_element_capacityInKB(int(size)) - disk_spec.set_element_device(disk_ctlr) - devices.append(disk_spec) - - -def add_cdrom(module, s, config_target, config, devices, default_devs, type="client", vm_cd_iso_path=None): - # Add a cd-rom - # Make sure the datastore exists. - if vm_cd_iso_path: - iso_location = vm_cd_iso_path.split('/', 1) - datastore, ds = find_datastore( - module, s, iso_location[0], config_target) - iso_path = iso_location[1] - - # find ide controller - ide_ctlr = None - for dev in default_devs: - if dev.typecode.type[1] == "VirtualIDEController": - ide_ctlr = dev - - # add a cdrom based on a physical device - if ide_ctlr: - cd_spec = config.new_deviceChange() - cd_spec.set_element_operation('add') - cd_ctrl = VI.ns0.VirtualCdrom_Def("cd_ctrl").pyclass() - - if type == "iso": - iso = VI.ns0.VirtualCdromIsoBackingInfo_Def("iso").pyclass() - ds_ref = iso.new_datastore(ds) - ds_ref.set_attribute_type(ds.get_attribute_type()) - iso.set_element_datastore(ds_ref) - iso.set_element_fileName("%s %s" % (datastore, iso_path)) - cd_ctrl.set_element_backing(iso) - cd_ctrl.set_element_key(20) - cd_ctrl.set_element_controllerKey(ide_ctlr.get_element_key()) - cd_ctrl.set_element_unitNumber(0) - cd_spec.set_element_device(cd_ctrl) - elif type == "client": - client = VI.ns0.VirtualCdromRemoteAtapiBackingInfo_Def( - "client").pyclass() - client.set_element_deviceName("") - cd_ctrl.set_element_backing(client) - cd_ctrl.set_element_key(20) - cd_ctrl.set_element_controllerKey(ide_ctlr.get_element_key()) - cd_ctrl.set_element_unitNumber(0) - cd_spec.set_element_device(cd_ctrl) - else: - s.disconnect() - module.fail_json( - msg="Error adding cdrom of type %s to vm spec. " - " cdrom type can either be iso or client" % (type)) - - devices.append(cd_spec) - - -def add_floppy(module, s, config_target, config, devices, default_devs, type="image", vm_floppy_image_path=None): - # Add a floppy - # Make sure the datastore exists. - if vm_floppy_image_path: - image_location = vm_floppy_image_path.split('/', 1) - datastore, ds = find_datastore( - module, s, image_location[0], config_target) - image_path = image_location[1] - - floppy_spec = config.new_deviceChange() - floppy_spec.set_element_operation('add') - floppy_ctrl = VI.ns0.VirtualFloppy_Def("floppy_ctrl").pyclass() - - if type == "image": - image = VI.ns0.VirtualFloppyImageBackingInfo_Def("image").pyclass() - ds_ref = image.new_datastore(ds) - ds_ref.set_attribute_type(ds.get_attribute_type()) - image.set_element_datastore(ds_ref) - image.set_element_fileName("%s %s" % (datastore, image_path)) - floppy_ctrl.set_element_backing(image) - floppy_ctrl.set_element_key(3) - floppy_spec.set_element_device(floppy_ctrl) - elif type == "client": - client = VI.ns0.VirtualFloppyRemoteDeviceBackingInfo_Def( - "client").pyclass() - client.set_element_deviceName("/dev/fd0") - floppy_ctrl.set_element_backing(client) - floppy_ctrl.set_element_key(3) - floppy_spec.set_element_device(floppy_ctrl) - else: - s.disconnect() - module.fail_json( - msg="Error adding floppy of type %s to vm spec. " - " floppy type can either be image or client" % (type)) - - devices.append(floppy_spec) - - -def add_nic(module, s, nfmor, config, devices, nic_type="vmxnet3", network_name="VM Network", network_type="standard", mac_address=None): - # add a NIC - # Different network card types are: "VirtualE1000", - # "VirtualE1000e","VirtualPCNet32", "VirtualVmxnet", "VirtualNmxnet2", - # "VirtualVmxnet3" - nic_spec = config.new_deviceChange() - nic_spec.set_element_operation("add") - - if nic_type == "e1000": - nic_ctlr = VI.ns0.VirtualE1000_Def("nic_ctlr").pyclass() - elif nic_type == "e1000e": - nic_ctlr = VI.ns0.VirtualE1000e_Def("nic_ctlr").pyclass() - elif nic_type == "pcnet32": - nic_ctlr = VI.ns0.VirtualPCNet32_Def("nic_ctlr").pyclass() - elif nic_type == "vmxnet": - nic_ctlr = VI.ns0.VirtualVmxnet_Def("nic_ctlr").pyclass() - elif nic_type == "vmxnet2": - nic_ctlr = VI.ns0.VirtualVmxnet2_Def("nic_ctlr").pyclass() - elif nic_type == "vmxnet3": - nic_ctlr = VI.ns0.VirtualVmxnet3_Def("nic_ctlr").pyclass() - else: - s.disconnect() - module.fail_json( - msg="Error adding nic to vm spec. No nic type of: %s" % - (nic_type)) - - if network_type == "standard": - nic_backing = VI.ns0.VirtualEthernetCardNetworkBackingInfo_Def( - "nic_backing").pyclass() - nic_backing.set_element_deviceName(network_name) - elif network_type == "dvs": - # Get the portgroup key - portgroupKey = find_portgroup_key(module, s, nfmor, network_name) - # Get the dvswitch uuid - dvswitch_uuid = find_dvswitch_uuid(module, s, nfmor, portgroupKey) - - nic_backing_port = VI.ns0.DistributedVirtualSwitchPortConnection_Def( - "nic_backing_port").pyclass() - nic_backing_port.set_element_switchUuid(dvswitch_uuid) - nic_backing_port.set_element_portgroupKey(portgroupKey) - - nic_backing = VI.ns0.VirtualEthernetCardDistributedVirtualPortBackingInfo_Def( - "nic_backing").pyclass() - nic_backing.set_element_port(nic_backing_port) - else: - s.disconnect() - module.fail_json( - msg="Error adding nic backing to vm spec. No network type of:" - " %s" % (network_type)) - - if mac_address: - nic_ctlr.set_element_addressType('manual') - nic_ctlr.set_element_macAddress(mac_address) - else: - nic_ctlr.set_element_addressType("generated") - nic_ctlr.set_element_backing(nic_backing) - nic_ctlr.set_element_key(4) - nic_spec.set_element_device(nic_ctlr) - devices.append(nic_spec) - - -def find_datastore(module, s, datastore, config_target): - # Verify the datastore exists and put it in brackets if it does. - ds = None - if config_target: - for d in config_target.Datastore: - if d.Datastore.Accessible and (datastore and d.Datastore.Name == datastore) or (not datastore): - ds = d.Datastore.Datastore - datastore = d.Datastore.Name - break - else: - for ds_mor, ds_name in s.get_datastores().items(): - ds_props = VIProperty(s, ds_mor) - if ds_props.summary.accessible and (datastore and ds_name == datastore) or (not datastore): - ds = ds_mor - datastore = ds_name - if not ds: - s.disconnect() - module.fail_json(msg="Datastore: %s does not appear to exist" % - (datastore)) - - datastore_name = "[%s]" % datastore - return datastore_name, ds - - -def find_portgroup_key(module, s, nfmor, network_name): - # Find a portgroups key given the portgroup name. - - # Grab all the distributed virtual portgroup's names and key's. - dvpg_mors = s._retrieve_properties_traversal( - property_names=['name', 'key'], - from_node=nfmor, obj_type='DistributedVirtualPortgroup') - - # Get the correct portgroup managed object. - dvpg_mor = None - for dvpg in dvpg_mors: - if dvpg_mor: - break - for p in dvpg.PropSet: - if p.Name == "name" and p.Val == network_name: - dvpg_mor = dvpg - if dvpg_mor: - break - - # If dvpg_mor is empty we didn't find the named portgroup. - if dvpg_mor is None: - s.disconnect() - module.fail_json( - msg="Could not find the distributed virtual portgroup named" - " %s" % network_name) - - # Get the portgroup key - portgroupKey = None - for p in dvpg_mor.PropSet: - if p.Name == "key": - portgroupKey = p.Val - - return portgroupKey - - -def find_dvswitch_uuid(module, s, nfmor, portgroupKey): - # Find a dvswitch's uuid given a portgroup key. - # Function searches all dvswitches in the datacenter to find the switch - # that has the portgroup key. - - # Grab the dvswitch uuid and portgroup properties - dvswitch_mors = s._retrieve_properties_traversal( - property_names=['uuid', 'portgroup'], - from_node=nfmor, obj_type='DistributedVirtualSwitch') - - dvswitch_mor = None - # Get the dvswitches managed object - for dvswitch in dvswitch_mors: - if dvswitch_mor: - break - for p in dvswitch.PropSet: - if p.Name == "portgroup": - pg_mors = p.Val.ManagedObjectReference - for pg_mor in pg_mors: - if dvswitch_mor: - break - key_mor = s._get_object_properties( - pg_mor, property_names=['key']) - for key in key_mor.PropSet: - if key.Val == portgroupKey: - dvswitch_mor = dvswitch - - # Get the switches uuid - dvswitch_uuid = None - for p in dvswitch_mor.PropSet: - if p.Name == "uuid": - dvswitch_uuid = p.Val - - return dvswitch_uuid - - -def spec_singleton(spec, request, vm): - - if not spec: - _this = request.new__this(vm._mor) - _this.set_attribute_type(vm._mor.get_attribute_type()) - request.set_element__this(_this) - spec = request.new_spec() - return spec - - -def get_cdrom_params(module, s, vm_cdrom): - cdrom_type = None - cdrom_iso_path = None - try: - cdrom_type = vm_cdrom['type'] - except KeyError: - s.disconnect() - module.fail_json( - msg="Error on %s definition. cdrom type needs to be" - " specified." % vm_cdrom) - if cdrom_type == 'iso': - try: - cdrom_iso_path = vm_cdrom['iso_path'] - except KeyError: - s.disconnect() - module.fail_json( - msg="Error on %s definition. cdrom iso_path needs" - " to be specified." % vm_cdrom) - - return cdrom_type, cdrom_iso_path - - -def vmdisk_id(vm, current_datastore_name): - id_list = [] - for vm_disk in vm._disks: - if current_datastore_name in vm_disk['descriptor']: - id_list.append(vm_disk['device']['key']) - return id_list - - -def deploy_template(vsphere_client, guest, resource_pool, template_src, esxi, module, cluster_name, snapshot_to_clone, power_on_after_clone, vm_extra_config): - vmTemplate = vsphere_client.get_vm_by_name(template_src) - vmTarget = None - - if esxi: - datacenter = esxi['datacenter'] - esxi_hostname = esxi['hostname'] - - # Datacenter managed object reference - dclist = [k for k, - v in vsphere_client.get_datacenters().items() if v == datacenter] - if dclist: - dcmor = dclist[0] - else: - vsphere_client.disconnect() - module.fail_json(msg="Cannot find datacenter named: %s" % datacenter) - - dcprops = VIProperty(vsphere_client, dcmor) - - # hostFolder managed reference - hfmor = dcprops.hostFolder._obj - - # Grab the computerResource name and host properties - crmors = vsphere_client._retrieve_properties_traversal( - property_names=['name', 'host'], - from_node=hfmor, - obj_type='ComputeResource') - - # Grab the host managed object reference of the esxi_hostname - try: - hostmor = [k for k, - v in vsphere_client.get_hosts().items() if v == esxi_hostname][0] - except IndexError: - vsphere_client.disconnect() - module.fail_json(msg="Cannot find esx host named: %s" % esxi_hostname) - - # Grab the computeResource managed object reference of the host we are - # creating the VM on. - crmor = None - for cr in crmors: - if crmor: - break - for p in cr.PropSet: - if p.Name == "host": - for h in p.Val.get_element_ManagedObjectReference(): - if h == hostmor: - crmor = cr.Obj - break - if crmor: - break - crprops = VIProperty(vsphere_client, crmor) - - rpmor = crprops.resourcePool._obj - elif resource_pool: - try: - cluster = [k for k, - v in vsphere_client.get_clusters().items() if v == cluster_name][0] if cluster_name else None - except IndexError: - vsphere_client.disconnect() - module.fail_json(msg="Cannot find Cluster named: %s" % - cluster_name) - - try: - rpmor = [k for k, v in vsphere_client.get_resource_pools( - from_mor=cluster).items() - if v == resource_pool][0] - except IndexError: - vsphere_client.disconnect() - module.fail_json(msg="Cannot find Resource Pool named: %s" % - resource_pool) - else: - module.fail_json(msg="You need to specify either esxi:[datacenter,hostname] or [cluster,resource_pool]") - - try: - vmTarget = vsphere_client.get_vm_by_name(guest) - except Exception: - pass - - if not vmTemplate.is_powered_off(): - module.fail_json( - msg="Source %s must be powered off" % template_src - ) - - try: - if not vmTarget: - cloneArgs = dict(resourcepool=rpmor, power_on=False) - - if snapshot_to_clone is not None: - # check if snapshot_to_clone is specified, Create a Linked Clone instead of a full clone. - cloneArgs["linked"] = True - cloneArgs["snapshot"] = snapshot_to_clone - - if vm_extra_config.get("folder") is not None: - # if a folder is specified, clone the VM into it - cloneArgs["folder"] = vm_extra_config.get("folder") - - vmTemplate.clone(guest, **cloneArgs) - - vm = vsphere_client.get_vm_by_name(guest) - - # VM was created. If there is any extra config options specified, set - if vm_extra_config: - vm.set_extra_config(vm_extra_config) - - # Power on if asked - if power_on_after_clone is True: - state = 'powered_on' - power_state(vm, state, True) - - changed = True - else: - changed = False - - vsphere_client.disconnect() - module.exit_json(changed=changed) - except Exception as e: - module.fail_json( - msg="Could not clone selected machine: %s" % e - ) - -# example from https://github.com/kalazzerx/pysphere/blob/master/examples/pysphere_create_disk_and_add_to_vm.py -# was used. - - -def update_disks(vsphere_client, vm, module, vm_disk, changes): - request = VI.ReconfigVM_TaskRequestMsg() - changed = False - - for cnf_disk in vm_disk: - disk_id = re.sub("disk", "", cnf_disk) - disk_type = vm_disk[cnf_disk]['type'] - found = False - for dev_key in vm._devices: - if vm._devices[dev_key]['type'] == 'VirtualDisk': - hdd_id = vm._devices[dev_key]['label'].split()[2] - if disk_id == hdd_id: - found = True - continue - if not found: - VI.ReconfigVM_TaskRequestMsg() - _this = request.new__this(vm._mor) - _this.set_attribute_type(vm._mor.get_attribute_type()) - request.set_element__this(_this) - - spec = request.new_spec() - - dc = spec.new_deviceChange() - dc.Operation = "add" - dc.FileOperation = "create" - - hd = VI.ns0.VirtualDisk_Def("hd").pyclass() - hd.Key = -100 - hd.UnitNumber = int(disk_id) - hd.CapacityInKB = int(vm_disk[cnf_disk]['size_gb']) * 1024 * 1024 - hd.ControllerKey = 1000 - - # module.fail_json(msg="peos : %s" % vm_disk[cnf_disk]) - backing = VI.ns0.VirtualDiskFlatVer2BackingInfo_Def("backing").pyclass() - backing.FileName = "[%s]" % vm_disk[cnf_disk]['datastore'] - backing.DiskMode = "persistent" - backing.Split = False - backing.WriteThrough = False - if disk_type == 'thin': - backing.ThinProvisioned = True - else: - backing.ThinProvisioned = False - backing.EagerlyScrub = False - hd.Backing = backing - - dc.Device = hd - - spec.DeviceChange = [dc] - request.set_element_spec(spec) - - ret = vsphere_client._proxy.ReconfigVM_Task(request)._returnval - - # Wait for the task to finish - task = VITask(ret, vsphere_client) - status = task.wait_for_state([task.STATE_SUCCESS, - task.STATE_ERROR]) - - if status == task.STATE_SUCCESS: - changed = True - changes[cnf_disk] = vm_disk[cnf_disk] - elif status == task.STATE_ERROR: - module.fail_json( - msg="Error reconfiguring vm: %s, [%s]" % ( - task.get_error_message(), - vm_disk[cnf_disk])) - return changed, changes - - -def reconfigure_vm(vsphere_client, vm, module, esxi, resource_pool, cluster_name, guest, vm_extra_config, vm_hardware, vm_disk, vm_nic, state, force): - spec = None - changed = False - changes = {} - request = None - shutdown = False - poweron = vm.is_powered_on() - devices = [] - - memoryHotAddEnabled = bool(vm.properties.config.memoryHotAddEnabled) - cpuHotAddEnabled = bool(vm.properties.config.cpuHotAddEnabled) - cpuHotRemoveEnabled = bool(vm.properties.config.cpuHotRemoveEnabled) - - changed, changes = update_disks(vsphere_client, vm, - module, vm_disk, changes) - vm.properties._flush_cache() - request = VI.ReconfigVM_TaskRequestMsg() - - # Change extra config - if vm_extra_config: - spec = spec_singleton(spec, request, vm) - extra_config = [] - for k, v in vm_extra_config.items(): - ec = spec.new_extraConfig() - ec.set_element_key(str(k)) - ec.set_element_value(str(v)) - extra_config.append(ec) - spec.set_element_extraConfig(extra_config) - changes["extra_config"] = vm_extra_config - - # Change Memory - if 'memory_mb' in vm_hardware: - - if int(vm_hardware['memory_mb']) != vm.properties.config.hardware.memoryMB: - spec = spec_singleton(spec, request, vm) - - if vm.is_powered_on(): - if force: - # No hot add but force - if not memoryHotAddEnabled: - shutdown = True - elif int(vm_hardware['memory_mb']) < vm.properties.config.hardware.memoryMB: - shutdown = True - else: - # Fail on no hot add and no force - if not memoryHotAddEnabled: - module.fail_json( - msg="memoryHotAdd is not enabled. force is " - "required for shutdown") - - # Fail on no force and memory shrink - elif int(vm_hardware['memory_mb']) < vm.properties.config.hardware.memoryMB: - module.fail_json( - msg="Cannot lower memory on a live VM. force is " - "required for shutdown") - - # set the new RAM size - spec.set_element_memoryMB(int(vm_hardware['memory_mb'])) - changes['memory'] = vm_hardware['memory_mb'] - # ===( Reconfigure Network )====# - if vm_nic: - changed = reconfigure_net(vsphere_client, vm, module, esxi, resource_pool, guest, vm_nic, cluster_name) - - # Change Num CPUs - if 'num_cpus' in vm_hardware: - if int(vm_hardware['num_cpus']) != vm.properties.config.hardware.numCPU: - spec = spec_singleton(spec, request, vm) - - if vm.is_powered_on(): - if force: - # No hot add but force - if not cpuHotAddEnabled: - shutdown = True - elif int(vm_hardware['num_cpus']) < vm.properties.config.hardware.numCPU: - if not cpuHotRemoveEnabled: - shutdown = True - else: - # Fail on no hot add and no force - if not cpuHotAddEnabled: - module.fail_json( - msg="cpuHotAdd is not enabled. force is " - "required for shutdown") - - # Fail on no force and cpu shrink without hot remove - elif int(vm_hardware['num_cpus']) < vm.properties.config.hardware.numCPU: - if not cpuHotRemoveEnabled: - module.fail_json( - msg="Cannot lower CPU on a live VM without " - "cpuHotRemove. force is required for shutdown") - - spec.set_element_numCPUs(int(vm_hardware['num_cpus'])) - - changes['cpu'] = vm_hardware['num_cpus'] - - # Change CDROM - if 'vm_cdrom' in vm_hardware: - spec = spec_singleton(spec, request, vm) - - cdrom_type, cdrom_iso_path = get_cdrom_params(module, vsphere_client, vm_hardware['vm_cdrom']) - - cdrom = None - current_devices = vm.properties.config.hardware.device - - for dev in current_devices: - if dev._type == 'VirtualCdrom': - cdrom = dev._obj - break - - if cdrom_type == 'iso': - iso_location = cdrom_iso_path.split('/', 1) - datastore, ds = find_datastore( - module, vsphere_client, iso_location[0], None) - iso_path = iso_location[1] - iso = VI.ns0.VirtualCdromIsoBackingInfo_Def('iso').pyclass() - iso.set_element_fileName('%s %s' % (datastore, iso_path)) - cdrom.set_element_backing(iso) - cdrom.Connectable.set_element_connected(True) - cdrom.Connectable.set_element_startConnected(True) - elif cdrom_type == 'client': - client = VI.ns0.VirtualCdromRemoteAtapiBackingInfo_Def('client').pyclass() - client.set_element_deviceName("") - cdrom.set_element_backing(client) - cdrom.Connectable.set_element_connected(True) - cdrom.Connectable.set_element_startConnected(True) - else: - vsphere_client.disconnect() - module.fail_json( - msg="Error adding cdrom of type %s to vm spec. " - " cdrom type can either be iso or client" % (cdrom_type)) - - dev_change = spec.new_deviceChange() - dev_change.set_element_device(cdrom) - dev_change.set_element_operation('edit') - devices.append(dev_change) - - changes['cdrom'] = vm_hardware['vm_cdrom'] - - # Resize hard drives - if vm_disk: - spec = spec_singleton(spec, request, vm) - - # Get a list of the VM's hard drives - dev_list = [d for d in vm.properties.config.hardware.device if d._type == 'VirtualDisk'] - if len(vm_disk) > len(dev_list): - vsphere_client.disconnect() - module.fail_json(msg="Error in vm_disk definition. Too many disks defined in comparison to the VM's disk profile.") - - disk_num = 0 - dev_changes = [] - disks_changed = {} - for disk in sorted(vm_disk): - try: - disksize = int(vm_disk[disk]['size_gb']) - # Convert the disk size to kilobytes - disksize = disksize * 1024 * 1024 - except (KeyError, ValueError): - vsphere_client.disconnect() - module.fail_json(msg="Error in '%s' definition. Size needs to be specified as an integer." % disk) - - # Make sure the new disk size is higher than the current value - dev = dev_list[disk_num] - if disksize < int(dev.capacityInKB): - vsphere_client.disconnect() - module.fail_json(msg="Error in '%s' definition. New size needs to be higher than the current value (%s GB)." % - (disk, int(dev.capacityInKB) / 1024 / 1024)) - - # Set the new disk size - elif disksize > int(dev.capacityInKB): - dev_obj = dev._obj - dev_obj.set_element_capacityInKB(disksize) - dev_change = spec.new_deviceChange() - dev_change.set_element_operation("edit") - dev_change.set_element_device(dev_obj) - dev_changes.append(dev_change) - disks_changed[disk] = {'size_gb': int(vm_disk[disk]['size_gb'])} - - disk_num = disk_num + 1 - - if dev_changes: - spec.set_element_deviceChange(dev_changes) - changes['disks'] = disks_changed - - if len(changes): - - if shutdown and vm.is_powered_on(): - try: - vm.power_off(sync_run=True) - vm.get_status() - - except Exception as e: - module.fail_json(msg='Failed to shutdown vm %s: %s' - % (guest, to_native(e)), - exception=traceback.format_exc()) - - if len(devices): - spec.set_element_deviceChange(devices) - - request.set_element_spec(spec) - ret = vsphere_client._proxy.ReconfigVM_Task(request)._returnval - - # Wait for the task to finish - task = VITask(ret, vsphere_client) - status = task.wait_for_state([task.STATE_SUCCESS, task.STATE_ERROR]) - if status == task.STATE_SUCCESS: - changed = True - elif status == task.STATE_ERROR: - module.fail_json( - msg="Error reconfiguring vm: %s" % task.get_error_message()) - - if vm.is_powered_off() and poweron: - try: - vm.power_on(sync_run=True) - except Exception as e: - module.fail_json( - msg='Failed to power on vm %s : %s' % (guest, to_native(e)), - exception=traceback.format_exc() - ) - - vsphere_client.disconnect() - if changed: - module.exit_json(changed=True, changes=changes) - - module.exit_json(changed=False) - - -def reconfigure_net(vsphere_client, vm, module, esxi, resource_pool, guest, vm_nic, cluster_name=None): - s = vsphere_client - nics = {} - request = VI.ReconfigVM_TaskRequestMsg() - _this = request.new__this(vm._mor) - _this.set_attribute_type(vm._mor.get_attribute_type()) - request.set_element__this(_this) - nic_changes = [] - datacenter = esxi['datacenter'] - # Datacenter managed object reference - dclist = [k for k, - v in vsphere_client.get_datacenters().items() if v == datacenter] - if dclist: - dcmor = dclist[0] - else: - vsphere_client.disconnect() - module.fail_json(msg="Cannot find datacenter named: %s" % datacenter) - dcprops = VIProperty(vsphere_client, dcmor) - nfmor = dcprops.networkFolder._obj - for k, v in vm_nic.items(): - nicNum = k[len(k) - 1] - if vm_nic[k]['network_type'] == 'dvs': - portgroupKey = find_portgroup_key(module, s, nfmor, vm_nic[k]['network']) - todvs = True - elif vm_nic[k]['network_type'] == 'standard': - todvs = False - # Detect cards that need to be changed and network type (and act accordingly) - for dev in vm.properties.config.hardware.device: - if dev._type in ["VirtualE1000", "VirtualE1000e", - "VirtualPCNet32", "VirtualVmxnet", - "VirtualNmxnet2", "VirtualVmxnet3"]: - devNum = dev.deviceInfo.label[len(dev.deviceInfo.label) - 1] - if devNum == nicNum: - fromdvs = dev.deviceInfo.summary.split(':')[0] == 'DVSwitch' - if todvs and fromdvs: - if dev.backing.port._obj.get_element_portgroupKey() != portgroupKey: - nics[k] = (dev, portgroupKey, 1) - elif fromdvs and not todvs: - nics[k] = (dev, '', 2) - elif not fromdvs and todvs: - nics[k] = (dev, portgroupKey, 3) - elif not fromdvs and not todvs: - if dev.backing._obj.get_element_deviceName() != vm_nic[k]['network']: - nics[k] = (dev, '', 2) - else: - pass - else: - module.exit_json() - - if len(nics) > 0: - for nic, obj in nics.items(): - """ - 1,2 and 3 are used to mark which action should be taken - 1 = from a distributed switch to a distributed switch - 2 = to a standard switch - 3 = to a distributed switch - """ - dev = obj[0] - pgKey = obj[1] - dvsKey = obj[2] - if dvsKey == 1: - dev.backing.port._obj.set_element_portgroupKey(pgKey) - dev.backing.port._obj.set_element_portKey('') - if dvsKey == 3: - dvswitch_uuid = find_dvswitch_uuid(module, s, nfmor, pgKey) - nic_backing_port = VI.ns0.DistributedVirtualSwitchPortConnection_Def( - "nic_backing_port").pyclass() - nic_backing_port.set_element_switchUuid(dvswitch_uuid) - nic_backing_port.set_element_portgroupKey(pgKey) - nic_backing_port.set_element_portKey('') - nic_backing = VI.ns0.VirtualEthernetCardDistributedVirtualPortBackingInfo_Def( - "nic_backing").pyclass() - nic_backing.set_element_port(nic_backing_port) - dev._obj.set_element_backing(nic_backing) - if dvsKey == 2: - nic_backing = VI.ns0.VirtualEthernetCardNetworkBackingInfo_Def( - "nic_backing").pyclass() - nic_backing.set_element_deviceName(vm_nic[nic]['network']) - dev._obj.set_element_backing(nic_backing) - for nic, obj in nics.items(): - dev = obj[0] - spec = request.new_spec() - nic_change = spec.new_deviceChange() - nic_change.set_element_device(dev._obj) - nic_change.set_element_operation("edit") - nic_changes.append(nic_change) - spec.set_element_deviceChange(nic_changes) - request.set_element_spec(spec) - ret = vsphere_client._proxy.ReconfigVM_Task(request)._returnval - task = VITask(ret, vsphere_client) - status = task.wait_for_state([task.STATE_SUCCESS, task.STATE_ERROR]) - if status == task.STATE_SUCCESS: - return(True) - elif status == task.STATE_ERROR: - module.fail_json(msg="Could not change network %s" % task.get_error_message()) - elif len(nics) == 0: - return(False) - - -def _build_folder_tree(nodes, parent): - tree = {} - - for node in nodes: - if node['parent'] == parent: - tree[node['name']] = dict.copy(node) - tree[node['name']]['subfolders'] = _build_folder_tree(nodes, node['id']) - del tree[node['name']]['parent'] - - return tree - - -def _find_path_in_tree(tree, path): - for name, o in tree.items(): - if name == path[0]: - if len(path) == 1: - return o - else: - return _find_path_in_tree(o['subfolders'], path[1:]) - - return None - - -def _get_folderid_for_path(vsphere_client, datacenter, path): - content = vsphere_client._retrieve_properties_traversal(property_names=['name', 'parent'], obj_type=MORTypes.Folder) - if not content: - return {} - - node_list = [ - { - 'id': o.Obj, - 'name': o.PropSet[0].Val, - 'parent': (o.PropSet[1].Val if len(o.PropSet) > 1 else None) - } for o in content - ] - - tree = _build_folder_tree(node_list, datacenter) - tree = _find_path_in_tree(tree, ['vm'])['subfolders'] - folder = _find_path_in_tree(tree, path.split('/')) - return folder['id'] if folder else None - - -def create_vm(vsphere_client, module, esxi, resource_pool, cluster_name, guest, vm_extra_config, vm_hardware, vm_disk, vm_nic, vm_hw_version, state): - - datacenter = esxi['datacenter'] - esxi_hostname = esxi['hostname'] - # Datacenter managed object reference - dclist = [k for k, - v in vsphere_client.get_datacenters().items() if v == datacenter] - if dclist: - dcmor = dclist[0] - else: - vsphere_client.disconnect() - module.fail_json(msg="Cannot find datacenter named: %s" % datacenter) - - dcprops = VIProperty(vsphere_client, dcmor) - - # hostFolder managed reference - hfmor = dcprops.hostFolder._obj - - # virtualmachineFolder managed object reference - if vm_extra_config.get('folder'): - # try to find the folder by its full path, e.g. 'production/customerA/lamp' - vmfmor = _get_folderid_for_path(vsphere_client, dcmor, vm_extra_config.get('folder')) - - # try the legacy behaviour of just matching the folder name, so 'lamp' alone matches 'production/customerA/lamp' - if vmfmor is None: - for mor, name in vsphere_client._get_managed_objects(MORTypes.Folder).items(): - if name == vm_extra_config['folder']: - vmfmor = mor - - # if neither of strategies worked, bail out - if vmfmor is None: - vsphere_client.disconnect() - module.fail_json(msg="Cannot find folder named: %s" % vm_extra_config['folder']) - else: - vmfmor = dcprops.vmFolder._obj - - # networkFolder managed object reference - nfmor = dcprops.networkFolder._obj - - # Grab the computerResource name and host properties - crmors = vsphere_client._retrieve_properties_traversal( - property_names=['name', 'host'], - from_node=hfmor, - obj_type='ComputeResource') - - # Grab the host managed object reference of the esxi_hostname - try: - hostmor = [k for k, - v in vsphere_client.get_hosts().items() if v == esxi_hostname][0] - except IndexError: - vsphere_client.disconnect() - module.fail_json(msg="Cannot find esx host named: %s" % esxi_hostname) - - # Grab the computerResource managed object reference of the host we are - # creating the VM on. - crmor = None - for cr in crmors: - if crmor: - break - for p in cr.PropSet: - if p.Name == "host": - for h in p.Val.get_element_ManagedObjectReference(): - if h == hostmor: - crmor = cr.Obj - break - if crmor: - break - crprops = VIProperty(vsphere_client, crmor) - - # Get resource pool managed reference - # Requires that a cluster name be specified. - if resource_pool: - try: - cluster = [k for k, - v in vsphere_client.get_clusters().items() if v == cluster_name][0] if cluster_name else None - except IndexError: - vsphere_client.disconnect() - module.fail_json(msg="Cannot find Cluster named: %s" % - cluster_name) - - try: - rpmor = [k for k, v in vsphere_client.get_resource_pools( - from_mor=cluster).items() - if v == resource_pool][0] - except IndexError: - vsphere_client.disconnect() - module.fail_json(msg="Cannot find Resource Pool named: %s" % - resource_pool) - - else: - rpmor = crprops.resourcePool._obj - - # CREATE VM CONFIGURATION - # get config target - request = VI.QueryConfigTargetRequestMsg() - _this = request.new__this(crprops.environmentBrowser._obj) - _this.set_attribute_type( - crprops.environmentBrowser._obj.get_attribute_type()) - request.set_element__this(_this) - h = request.new_host(hostmor) - h.set_attribute_type(hostmor.get_attribute_type()) - request.set_element_host(h) - config_target = vsphere_client._proxy.QueryConfigTarget(request)._returnval - - # get default devices - request = VI.QueryConfigOptionRequestMsg() - _this = request.new__this(crprops.environmentBrowser._obj) - _this.set_attribute_type( - crprops.environmentBrowser._obj.get_attribute_type()) - request.set_element__this(_this) - h = request.new_host(hostmor) - h.set_attribute_type(hostmor.get_attribute_type()) - request.set_element_host(h) - config_option = vsphere_client._proxy.QueryConfigOption(request)._returnval - default_devs = config_option.DefaultDevice - - # add parameters to the create vm task - create_vm_request = VI.CreateVM_TaskRequestMsg() - config = create_vm_request.new_config() - if vm_hw_version: - config.set_element_version(vm_hw_version) - vmfiles = config.new_files() - datastore_name, ds = find_datastore( - module, vsphere_client, vm_disk['disk1']['datastore'], config_target) - vmfiles.set_element_vmPathName(datastore_name) - config.set_element_files(vmfiles) - config.set_element_name(guest) - if 'notes' in vm_extra_config: - config.set_element_annotation(vm_extra_config['notes']) - config.set_element_memoryMB(int(vm_hardware['memory_mb'])) - config.set_element_numCPUs(int(vm_hardware['num_cpus'])) - config.set_element_guestId(vm_hardware['osid']) - devices = [] - - # Attach all the hardware we want to the VM spec. - # Add a scsi controller to the VM spec. - disk_ctrl_key = add_scsi_controller( - module, vsphere_client, config, devices, vm_hardware['scsi']) - if vm_disk: - disk_num = 0 - disk_key = 0 - bus_num = 0 - disk_ctrl = 1 - for disk in sorted(vm_disk): - try: - datastore = vm_disk[disk]['datastore'] - except KeyError: - vsphere_client.disconnect() - module.fail_json( - msg="Error on %s definition. datastore needs to be" - " specified." % disk) - try: - disksize = int(vm_disk[disk]['size_gb']) - # Convert the disk size to kiloboytes - disksize = disksize * 1024 * 1024 - except (KeyError, ValueError): - vsphere_client.disconnect() - module.fail_json(msg="Error on %s definition. size needs to be specified as an integer." % disk) - try: - disktype = vm_disk[disk]['type'] - except KeyError: - vsphere_client.disconnect() - module.fail_json( - msg="Error on %s definition. type needs to be" - " specified." % disk) - if disk_num == 7: - disk_num = disk_num + 1 - disk_key = disk_key + 1 - elif disk_num > 15: - bus_num = bus_num + 1 - disk_ctrl = disk_ctrl + 1 - disk_ctrl_key = add_scsi_controller( - module, vsphere_client, config, devices, type=vm_hardware['scsi'], bus_num=bus_num, disk_ctrl_key=disk_ctrl) - disk_num = 0 - disk_key = 0 - # Add the disk to the VM spec. - add_disk( - module, vsphere_client, config_target, config, - devices, datastore, disktype, disksize, disk_ctrl_key, - disk_num, disk_key) - disk_num = disk_num + 1 - disk_key = disk_key + 1 - if 'vm_cdrom' in vm_hardware: - cdrom_type, cdrom_iso_path = get_cdrom_params(module, vsphere_client, vm_hardware['vm_cdrom']) - # Add a CD-ROM device to the VM. - add_cdrom(module, vsphere_client, config_target, config, devices, - default_devs, cdrom_type, cdrom_iso_path) - if 'vm_floppy' in vm_hardware: - floppy_image_path = None - floppy_type = None - try: - floppy_type = vm_hardware['vm_floppy']['type'] - except KeyError: - vsphere_client.disconnect() - module.fail_json( - msg="Error on %s definition. floppy type needs to be" - " specified." % vm_hardware['vm_floppy']) - if floppy_type == 'image': - try: - floppy_image_path = vm_hardware['vm_floppy']['image_path'] - except KeyError: - vsphere_client.disconnect() - module.fail_json( - msg="Error on %s definition. floppy image_path needs" - " to be specified." % vm_hardware['vm_floppy']) - # Add a floppy to the VM. - add_floppy(module, vsphere_client, config_target, config, devices, - default_devs, floppy_type, floppy_image_path) - if vm_nic: - for nic in sorted(vm_nic): - try: - nictype = vm_nic[nic]['type'] - except KeyError: - vsphere_client.disconnect() - module.fail_json( - msg="Error on %s definition. type needs to be " - " specified." % nic) - try: - network = vm_nic[nic]['network'] - except KeyError: - vsphere_client.disconnect() - module.fail_json( - msg="Error on %s definition. network needs to be " - " specified." % nic) - try: - network_type = vm_nic[nic]['network_type'] - except KeyError: - vsphere_client.disconnect() - module.fail_json( - msg="Error on %s definition. network_type needs to be " - " specified." % nic) - mac_address = vm_nic[nic]['mac_address'] if 'mac_address' in vm_nic[nic] else None - # Add the nic to the VM spec. - add_nic(module, vsphere_client, nfmor, config, devices, - nictype, network, network_type, mac_address) - - config.set_element_deviceChange(devices) - create_vm_request.set_element_config(config) - folder_mor = create_vm_request.new__this(vmfmor) - folder_mor.set_attribute_type(vmfmor.get_attribute_type()) - create_vm_request.set_element__this(folder_mor) - rp_mor = create_vm_request.new_pool(rpmor) - rp_mor.set_attribute_type(rpmor.get_attribute_type()) - create_vm_request.set_element_pool(rp_mor) - host_mor = create_vm_request.new_host(hostmor) - host_mor.set_attribute_type(hostmor.get_attribute_type()) - create_vm_request.set_element_host(host_mor) - - # CREATE THE VM - taskmor = vsphere_client._proxy.CreateVM_Task(create_vm_request)._returnval - task = VITask(taskmor, vsphere_client) - task.wait_for_state([task.STATE_SUCCESS, task.STATE_ERROR]) - if task.get_state() == task.STATE_ERROR: - vsphere_client.disconnect() - module.fail_json(msg="Error creating vm: %s" % - task.get_error_message()) - else: - # We always need to get the vm because we are going to gather facts - vm = vsphere_client.get_vm_by_name(guest) - - # VM was created. If there is any extra config options specified, set - # them here , disconnect from vcenter, then exit. - if vm_extra_config: - vm.set_extra_config(vm_extra_config) - - # Power on the VM if it was requested - power_state(vm, state, True) - - vmfacts = gather_facts(vm) - vsphere_client.disconnect() - module.exit_json( - ansible_facts=vmfacts, - changed=True, - changes="Created VM %s" % guest) - - -def delete_vm(vsphere_client, module, guest, vm, force): - try: - - if vm.is_powered_on(): - if force: - try: - vm.power_off(sync_run=True) - vm.get_status() - - except Exception as e: - module.fail_json( - msg='Failed to shutdown vm %s: %s' % (guest, to_native(e)), - exception=traceback.format_exc()) - else: - module.fail_json( - msg='You must use either shut the vm down first or ' - 'use force ') - - # Invoke Destroy_Task - request = VI.Destroy_TaskRequestMsg() - _this = request.new__this(vm._mor) - _this.set_attribute_type(vm._mor.get_attribute_type()) - request.set_element__this(_this) - ret = vsphere_client._proxy.Destroy_Task(request)._returnval - task = VITask(ret, vsphere_client) - - # Wait for the task to finish - status = task.wait_for_state( - [task.STATE_SUCCESS, task.STATE_ERROR]) - if status == task.STATE_ERROR: - vsphere_client.disconnect() - module.fail_json(msg="Error removing vm: %s %s" % - task.get_error_message()) - module.exit_json(changed=True, changes="VM %s deleted" % guest) - except Exception as e: - module.fail_json( - msg='Failed to delete vm %s : %s' % (guest, to_native(e)), - exception=traceback.format_exc()) - - -def power_state(vm, state, force): - """ - Correctly set the power status for a VM determined by the current and - requested states. force is forceful - """ - power_status = vm.get_status() - - check_status = ' '.join(state.split("_")).upper() - - # Need Force - if not force and power_status in [ - 'SUSPENDED', 'POWERING ON', - 'RESETTING', 'BLOCKED ON MSG' - ]: - - return "VM is in %s power state. Force is required!" % power_status - - # State is already true - if power_status == check_status: - return False - - else: - try: - if state == 'powered_off': - vm.power_off(sync_run=True) - elif state == 'poweroff_guest': - if power_status in ('POWERED ON'): - vm.shutdown_guest() - elif power_status in ('POWERED OFF'): - return False - else: - return "Cannot poweroff_guest VM in the current state %s" \ - % power_status - - elif state == 'powered_on': - vm.power_on(sync_run=True) - - elif state == 'reboot_guest': - if power_status in ('POWERED ON'): - vm.reboot_guest() - elif power_status in ('POWERED OFF'): - vm.power_on(sync_run=True) - else: - return "Cannot reboot_guest VM in the current state %s" \ - % power_status - - elif state == 'restarted': - if power_status in ('POWERED ON', 'POWERING ON', 'RESETTING'): - vm.reset(sync_run=False) - else: - return "Cannot restart VM in the current state %s" \ - % power_status - return True - - except Exception as e: - return e - - return False - - -def gather_facts(vm): - """ - Gather facts for VM directly from vsphere. - """ - vm.get_properties() - facts = { - 'module_hw': True, - 'hw_name': vm.properties.name, - 'hw_power_status': vm.get_status(), - 'hw_guest_full_name': vm.properties.config.guestFullName, - 'hw_guest_id': vm.properties.config.guestId, - 'hw_product_uuid': vm.properties.config.uuid, - 'hw_instance_uuid': vm.properties.config.instanceUuid, - 'hw_processor_count': vm.properties.config.hardware.numCPU, - 'hw_memtotal_mb': vm.properties.config.hardware.memoryMB, - 'hw_interfaces': [], - } - netInfo = vm.get_property('net') - netDict = {} - if netInfo: - for net in netInfo: - netDict[net['mac_address']] = net['ip_addresses'] - - ifidx = 0 - for entry in vm.properties.config.hardware.device: - - if not hasattr(entry, 'macAddress'): - continue - - factname = 'hw_eth' + str(ifidx) - facts[factname] = { - 'addresstype': entry.addressType, - 'label': entry.deviceInfo.label, - 'macaddress': entry.macAddress, - 'ipaddresses': netDict.get(entry.macAddress, None), - 'macaddress_dash': entry.macAddress.replace(':', '-'), - 'summary': entry.deviceInfo.summary, - } - facts['hw_interfaces'].append('eth' + str(ifidx)) - - ifidx += 1 - - return facts - - -class DefaultVMConfig(object): - - """ - Shallow and deep dict comparison for interfaces - """ - - def __init__(self, check_dict, interface_dict): - self.check_dict, self.interface_dict = check_dict, interface_dict - self.set_current, self.set_past = set( - check_dict.keys()), set(interface_dict.keys()) - self.intersect = self.set_current.intersection(self.set_past) - self.recursive_missing = None - - def shallow_diff(self): - return self.set_past - self.intersect - - def recursive_diff(self): - - if not self.recursive_missing: - self.recursive_missing = [] - for key, value in self.interface_dict.items(): - if isinstance(value, dict): - for k, v in value.items(): - if k in self.check_dict[key]: - if not isinstance(self.check_dict[key][k], v): - try: - if v == int: - self.check_dict[key][k] = int(self.check_dict[key][k]) - elif v == string_types: - self.check_dict[key][k] = to_native(self.check_dict[key][k], - errors='surrogate_or_strict') - else: - raise ValueError - except ValueError: - self.recursive_missing.append((k, v)) - else: - self.recursive_missing.append((k, v)) - - return self.recursive_missing - - -def config_check(name, passed, default, module): - """ - Checks that the dict passed for VM configuration matches the required - interface declared at the top of __main__ - """ - - diff = DefaultVMConfig(passed, default) - if len(diff.shallow_diff()): - module.fail_json( - msg="Missing required key/pair [%s]. %s must contain %s" % - (', '.join(diff.shallow_diff()), name, default)) - - if diff.recursive_diff(): - module.fail_json( - msg="Config mismatch for %s on %s" % - (name, diff.recursive_diff())) - - return True - - -def main(): - - vm = None - - proto_vm_hardware = { - 'memory_mb': int, - 'num_cpus': int, - 'scsi': string_types, - 'osid': string_types - } - - proto_vm_disk = { - 'disk1': { - 'datastore': string_types, - 'size_gb': int, - 'type': string_types - } - } - - proto_vm_nic = { - 'nic1': { - 'type': string_types, - 'network': string_types, - 'network_type': string_types - } - } - - proto_esxi = { - 'datacenter': string_types, - 'hostname': string_types - } - - module = AnsibleModule( - argument_spec=dict( - vcenter_hostname=dict( - type='str', - default=os.environ.get('VMWARE_HOST') - ), - username=dict( - type='str', - default=os.environ.get('VMWARE_USER') - ), - password=dict( - type='str', no_log=True, - default=os.environ.get('VMWARE_PASSWORD') - ), - state=dict( - required=False, - choices=[ - 'powered_on', - 'powered_off', - 'present', - 'absent', - 'restarted', - 'reconfigured', - 'reboot_guest', - 'poweroff_guest' - ], - default='present'), - vmware_guest_facts=dict(required=False, type='bool'), - from_template=dict(required=False, type='bool'), - template_src=dict(required=False, type='str'), - snapshot_to_clone=dict(required=False, default=None, type='str'), - guest=dict(required=True, type='str'), - vm_disk=dict(required=False, type='dict', default={}), - vm_nic=dict(required=False, type='dict', default={}), - vm_hardware=dict(required=False, type='dict', default={}), - vm_extra_config=dict(required=False, type='dict', default={}), - vm_hw_version=dict(required=False, default=None, type='str'), - resource_pool=dict(required=False, default=None, type='str'), - cluster=dict(required=False, default=None, type='str'), - force=dict(required=False, type='bool', default=False), - esxi=dict(required=False, type='dict', default={}), - validate_certs=dict(required=False, type='bool', default=True), - power_on_after_clone=dict(required=False, type='bool', default=True) - - - ), - supports_check_mode=False, - mutually_exclusive=[['state', 'vmware_guest_facts'], ['state', 'from_template']], - required_together=[ - ['state', 'force'], - [ - 'state', - 'vm_disk', - 'vm_nic', - 'vm_hardware', - 'esxi' - ], - ['from_template', 'template_src'], - ], - ) - - module.deprecate("The 'vsphere_guest' module has been deprecated. Use 'vmware_guest' instead.", version=2.9) - - if not HAS_PYSPHERE: - module.fail_json(msg='pysphere module required') - - vcenter_hostname = module.params['vcenter_hostname'] - username = module.params['username'] - password = module.params['password'] - vmware_guest_facts = module.params['vmware_guest_facts'] - state = module.params['state'] - guest = module.params['guest'] - force = module.params['force'] - vm_disk = module.params['vm_disk'] - vm_nic = module.params['vm_nic'] - vm_hardware = module.params['vm_hardware'] - vm_extra_config = module.params['vm_extra_config'] - vm_hw_version = module.params['vm_hw_version'] - esxi = module.params['esxi'] - resource_pool = module.params['resource_pool'] - cluster = module.params['cluster'] - template_src = module.params['template_src'] - from_template = module.params['from_template'] - snapshot_to_clone = module.params['snapshot_to_clone'] - power_on_after_clone = module.params['power_on_after_clone'] - validate_certs = module.params['validate_certs'] - - # CONNECT TO THE SERVER - viserver = VIServer() - if validate_certs and not hasattr(ssl, 'SSLContext') and not vcenter_hostname.startswith('http://'): - module.fail_json(msg='pysphere does not support verifying certificates with python < 2.7.9. Either update python or set ' - 'validate_certs=False on the task') - - if not validate_certs and hasattr(ssl, 'SSLContext'): - ssl._create_default_https_context = ssl._create_unverified_context - - try: - viserver.connect(vcenter_hostname, username, password) - except ssl.SSLError as sslerr: - module.fail_json(msg='Unable to validate the certificate of the vcenter hostname %s. Due to %s' % (vcenter_hostname, sslerr)) - except socket.gaierror as err: - module.fail_json(msg="Unable to resolve name for vcenter hostname: %s. Due to %s" % (vcenter_hostname, to_native(err))) - except (TypeError, VIApiException) as err: - module.fail_json(msg="Cannot connect to %s: %s" % (vcenter_hostname, to_native(err)), - exception=traceback.format_exc()) - - # Check if the VM exists before continuing - try: - vm = viserver.get_vm_by_name(guest) - except Exception: - pass - - if vm: - # Run for facts only - if vmware_guest_facts: - try: - module.exit_json(ansible_facts=gather_facts(vm)) - except Exception as e: - module.fail_json(msg="Fact gather failed with exception %s" - % to_native(e), exception=traceback.format_exc()) - # Power Changes - elif state in ['powered_on', 'powered_off', 'restarted', 'reboot_guest', 'poweroff_guest']: - state_result = power_state(vm, state, force) - - # Failure - if isinstance(state_result, string_types): - module.fail_json(msg=state_result) - else: - module.exit_json(changed=state_result) - - # Just check if there - elif state == 'present': - module.exit_json(changed=False) - - # Fail on reconfig without params - elif state == 'reconfigured': - reconfigure_vm( - vsphere_client=viserver, - vm=vm, - module=module, - esxi=esxi, - resource_pool=resource_pool, - cluster_name=cluster, - guest=guest, - vm_extra_config=vm_extra_config, - vm_hardware=vm_hardware, - vm_disk=vm_disk, - vm_nic=vm_nic, - state=state, - force=force - ) - elif state == 'absent': - delete_vm( - vsphere_client=viserver, - module=module, - guest=guest, - vm=vm, - force=force) - - # VM doesn't exist - else: - - # Fail for fact gather task - if vmware_guest_facts: - module.fail_json( - msg="No such VM %s. Fact gathering requires an existing vm" - % guest) - - elif from_template: - deploy_template( - vsphere_client=viserver, - esxi=esxi, - resource_pool=resource_pool, - guest=guest, - template_src=template_src, - module=module, - cluster_name=cluster, - snapshot_to_clone=snapshot_to_clone, - power_on_after_clone=power_on_after_clone, - vm_extra_config=vm_extra_config - ) - - if state in ['restarted', 'reconfigured']: - module.fail_json( - msg="No such VM %s. States [" - "restarted, reconfigured] required an existing VM" % guest) - elif state == 'absent': - module.exit_json(changed=False, msg="vm %s not present" % guest) - - # check if user is trying to perform state operation on a vm which doesn't exists - elif state in ['present', 'powered_off', 'powered_on'] and not all((vm_extra_config, - vm_hardware, vm_disk, vm_nic, esxi)): - module.exit_json(changed=False, msg="vm %s not present" % guest) - - # Create the VM - elif state in ['present', 'powered_off', 'powered_on']: - - # Check the guest_config - config_check("vm_disk", vm_disk, proto_vm_disk, module) - config_check("vm_nic", vm_nic, proto_vm_nic, module) - config_check("vm_hardware", vm_hardware, proto_vm_hardware, module) - config_check("esxi", esxi, proto_esxi, module) - - create_vm( - vsphere_client=viserver, - module=module, - esxi=esxi, - resource_pool=resource_pool, - cluster_name=cluster, - guest=guest, - vm_extra_config=vm_extra_config, - vm_hardware=vm_hardware, - vm_disk=vm_disk, - vm_nic=vm_nic, - vm_hw_version=vm_hw_version, - state=state - ) - - viserver.disconnect() - module.exit_json( - changed=False, - vcenter=vcenter_hostname) +from ansible.module_utils.common.removed import removed_module if __name__ == '__main__': - main() + removed_module(removed_in='2.9') diff --git a/lib/ansible/modules/clustering/k8s/_kubernetes.py b/lib/ansible/modules/clustering/k8s/_kubernetes.py index 34ceb6c5136..f039d827b70 100644 --- a/lib/ansible/modules/clustering/k8s/_kubernetes.py +++ b/lib/ansible/modules/clustering/k8s/_kubernetes.py @@ -7,422 +7,12 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['deprecated'], + 'status': ['removed'], 'supported_by': 'community'} -DOCUMENTATION = ''' ---- -module: kubernetes -version_added: "2.1" -deprecated: - removed_in: "2.9" - why: This module used the oc command line tool, where as M(k8s_raw) goes over the REST API. - alternative: Use M(k8s_raw) instead. -short_description: Manage Kubernetes resources -description: - - This module can manage Kubernetes resources on an existing cluster using - the Kubernetes server API. Users can specify in-line API data, or - specify an existing Kubernetes YAML file. - - Currently, this module - (1) Only supports HTTP Basic Auth - (2) Only supports 'strategic merge' for update, http://goo.gl/fCPYxT - SSL certs are not working, use C(validate_certs=off) to disable. -options: - api_endpoint: - description: - - The IPv4 API endpoint of the Kubernetes cluster. - required: true - aliases: [ endpoint ] - inline_data: - description: - - The Kubernetes YAML data to send to the API I(endpoint). This option is - mutually exclusive with C('file_reference'). - required: true - file_reference: - description: - - Specify full path to a Kubernets YAML file to send to API I(endpoint). - This option is mutually exclusive with C('inline_data'). - patch_operation: - description: - - Specify patch operation for Kubernetes resource update. - - For details, see the description of PATCH operations at - U(https://github.com/kubernetes/kubernetes/blob/release-1.5/docs/devel/api-conventions.md#patch-operations). - default: Strategic Merge Patch - choices: [ JSON Patch, Merge Patch, Strategic Merge Patch ] - aliases: [ patch_strategy ] - version_added: 2.4 - certificate_authority_data: - description: - - Certificate Authority data for Kubernetes server. Should be in either - standard PEM format or base64 encoded PEM data. Note that certificate - verification is broken until ansible supports a version of - 'match_hostname' that can match the IP address against the CA data. - state: - description: - - The desired action to take on the Kubernetes data. - required: true - choices: [ absent, present, replace, update ] - default: present - url_password: - description: - - The HTTP Basic Auth password for the API I(endpoint). This should be set - unless using the C('insecure') option. - aliases: [ password ] - url_username: - description: - - The HTTP Basic Auth username for the API I(endpoint). This should be set - unless using the C('insecure') option. - default: admin - aliases: [ username ] - insecure: - description: - - Reverts the connection to using HTTP instead of HTTPS. This option should - only be used when execuing the M('kubernetes') module local to the Kubernetes - cluster using the insecure local port (locahost:8080 by default). - validate_certs: - description: - - Enable/disable certificate validation. Note that this is set to - C(false) until Ansible can support IP address based certificate - hostname matching (exists in >= python3.5.0). - type: bool - default: 'no' -author: -- Eric Johnson (@erjohnso) -''' -EXAMPLES = ''' -# Create a new namespace with in-line YAML. -- name: Create a kubernetes namespace - kubernetes: - api_endpoint: 123.45.67.89 - url_username: admin - url_password: redacted - inline_data: - kind: Namespace - apiVersion: v1 - metadata: - name: ansible-test - labels: - label_env: production - label_ver: latest - annotations: - a1: value1 - a2: value2 - state: present - -# Create a new namespace from a YAML file. -- name: Create a kubernetes namespace - kubernetes: - api_endpoint: 123.45.67.89 - url_username: admin - url_password: redacted - file_reference: /path/to/create_namespace.yaml - state: present - -# Do the same thing, but using the insecure localhost port -- name: Create a kubernetes namespace - kubernetes: - api_endpoint: 123.45.67.89 - insecure: true - file_reference: /path/to/create_namespace.yaml - state: present - -''' - -RETURN = ''' -# Example response from creating a Kubernetes Namespace. -api_response: - description: Raw response from Kubernetes API, content varies with API. - returned: success - type: complex - contains: - apiVersion: "v1" - kind: "Namespace" - metadata: - creationTimestamp: "2016-01-04T21:16:32Z" - name: "test-namespace" - resourceVersion: "509635" - selfLink: "/api/v1/namespaces/test-namespace" - uid: "6dbd394e-b328-11e5-9a02-42010af0013a" - spec: - finalizers: - - kubernetes - status: - phase: "Active" -''' - -import base64 -import json -import traceback - -YAML_IMP_ERR = None -try: - import yaml - HAS_LIB_YAML = True -except ImportError: - YAML_IMP_ERR = traceback.format_exc() - HAS_LIB_YAML = False - -from ansible.module_utils.basic import AnsibleModule, missing_required_lib -from ansible.module_utils.urls import fetch_url - - -############################################################################ -############################################################################ -# For API coverage, this Anislbe module provides capability to operate on -# all Kubernetes objects that support a "create" call (except for 'Events'). -# In order to obtain a valid list of Kubernetes objects, the v1 spec file -# was referenced and the below python script was used to parse the JSON -# spec file, extract only the objects with a description starting with -# 'create a'. The script then iterates over all of these base objects -# to get the endpoint URL and was used to generate the KIND_URL map. -# -# import json -# from urllib2 import urlopen -# -# r = urlopen("https://raw.githubusercontent.com/kubernetes" -# "/kubernetes/master/api/swagger-spec/v1.json") -# v1 = json.load(r) -# -# apis = {} -# for a in v1['apis']: -# p = a['path'] -# for o in a['operations']: -# if o["summary"].startswith("create a") and o["type"] != "v1.Event": -# apis[o["type"]] = p -# -# def print_kind_url_map(): -# results = [] -# for a in apis.keys(): -# results.append('"%s": "%s"' % (a[3:].lower(), apis[a])) -# results.sort() -# print("KIND_URL = {") -# print(",\n".join(results)) -# print("}") -# -# if __name__ == '__main__': -# print_kind_url_map() -############################################################################ -############################################################################ - -KIND_URL = { - "binding": "/api/v1/namespaces/{namespace}/bindings", - "configmap": "/api/v1/namespaces/{namespace}/configmaps", - "endpoints": "/api/v1/namespaces/{namespace}/endpoints", - "limitrange": "/api/v1/namespaces/{namespace}/limitranges", - "namespace": "/api/v1/namespaces", - "node": "/api/v1/nodes", - "persistentvolume": "/api/v1/persistentvolumes", - "persistentvolumeclaim": "/api/v1/namespaces/{namespace}/persistentvolumeclaims", # NOQA - "pod": "/api/v1/namespaces/{namespace}/pods", - "podtemplate": "/api/v1/namespaces/{namespace}/podtemplates", - "replicationcontroller": "/api/v1/namespaces/{namespace}/replicationcontrollers", # NOQA - "resourcequota": "/api/v1/namespaces/{namespace}/resourcequotas", - "secret": "/api/v1/namespaces/{namespace}/secrets", - "service": "/api/v1/namespaces/{namespace}/services", - "serviceaccount": "/api/v1/namespaces/{namespace}/serviceaccounts", - "daemonset": "/apis/extensions/v1beta1/namespaces/{namespace}/daemonsets", - "deployment": "/apis/extensions/v1beta1/namespaces/{namespace}/deployments", - "horizontalpodautoscaler": "/apis/extensions/v1beta1/namespaces/{namespace}/horizontalpodautoscalers", # NOQA - "ingress": "/apis/extensions/v1beta1/namespaces/{namespace}/ingresses", - "job": "/apis/extensions/v1beta1/namespaces/{namespace}/jobs", -} -USER_AGENT = "ansible-k8s-module/0.0.1" - - -# TODO(erjohnso): SSL Certificate validation is currently unsupported. -# It can be made to work when the following are true: -# - Ansible consistently uses a "match_hostname" that supports IP Address -# matching. This is now true in >= python3.5.0. Currently, this feature -# is not yet available in backports.ssl_match_hostname (still 3.4). -# - Ansible allows passing in the self-signed CA cert that is created with -# a kubernetes master. The lib/ansible/module_utils/urls.py method, -# SSLValidationHandler.get_ca_certs() needs a way for the Kubernetes -# CA cert to be passed in and included in the generated bundle file. -# When this is fixed, the following changes can be made to this module, -# - Remove the 'return' statement in line 254 below -# - Set 'required=true' for certificate_authority_data and ensure that -# ansible's SSLValidationHandler.get_ca_certs() can pick up this CA cert -# - Set 'required=true' for the validate_certs param. - -def decode_cert_data(module): - return - # pylint: disable=unreachable - d = module.params.get("certificate_authority_data") - if d and not d.startswith("-----BEGIN"): - module.params["certificate_authority_data"] = base64.b64decode(d) - - -def api_request(module, url, method="GET", headers=None, data=None): - body = None - if data: - data = json.dumps(data) - response, info = fetch_url(module, url, method=method, headers=headers, data=data) - if int(info['status']) == -1: - module.fail_json(msg="Failed to execute the API request: %s" % info['msg'], url=url, method=method, headers=headers) - if response is not None: - body = json.loads(response.read()) - return info, body - - -def k8s_create_resource(module, url, data): - info, body = api_request(module, url, method="POST", data=data, headers={"Content-Type": "application/json"}) - if info['status'] == 409: - name = data["metadata"].get("name", None) - info, body = api_request(module, url + "/" + name) - return False, body - elif info['status'] >= 400: - module.fail_json(msg="failed to create the resource: %s" % info['msg'], url=url) - return True, body - - -def k8s_delete_resource(module, url, data): - name = data.get('metadata', {}).get('name') - if name is None: - module.fail_json(msg="Missing a named resource in object metadata when trying to remove a resource") - - url = url + '/' + name - info, body = api_request(module, url, method="DELETE") - if info['status'] == 404: - return False, "Resource name '%s' already absent" % name - elif info['status'] >= 400: - module.fail_json(msg="failed to delete the resource '%s': %s" % (name, info['msg']), url=url) - return True, "Successfully deleted resource name '%s'" % name - - -def k8s_replace_resource(module, url, data): - name = data.get('metadata', {}).get('name') - if name is None: - module.fail_json(msg="Missing a named resource in object metadata when trying to replace a resource") - - headers = {"Content-Type": "application/json"} - url = url + '/' + name - info, body = api_request(module, url, method="PUT", data=data, headers=headers) - if info['status'] == 409: - name = data["metadata"].get("name", None) - info, body = api_request(module, url + "/" + name) - return False, body - elif info['status'] >= 400: - module.fail_json(msg="failed to replace the resource '%s': %s" % (name, info['msg']), url=url) - return True, body - - -def k8s_update_resource(module, url, data, patch_operation): - # PATCH operations are explained in details at: - # https://github.com/kubernetes/kubernetes/blob/release-1.5/docs/devel/api-conventions.md#patch-operations - PATCH_OPERATIONS_MAP = { - 'JSON Patch': 'application/json-patch+json', - 'Merge Patch': 'application/merge-patch+json', - 'Strategic Merge Patch': 'application/strategic-merge-patch+json', - } - - name = data.get('metadata', {}).get('name') - if name is None: - module.fail_json(msg="Missing a named resource in object metadata when trying to update a resource") - - headers = {"Content-Type": PATCH_OPERATIONS_MAP[patch_operation]} - url = url + '/' + name - info, body = api_request(module, url, method="PATCH", data=data, headers=headers) - if info['status'] == 409: - name = data["metadata"].get("name", None) - info, body = api_request(module, url + "/" + name) - return False, body - elif info['status'] >= 400: - module.fail_json(msg="failed to update the resource '%s': %s" % (name, info['msg']), url=url) - return True, body - - -def main(): - module = AnsibleModule( - argument_spec=dict( - http_agent=dict(type='str', default=USER_AGENT), - url_username=dict(type='str', default='admin', aliases=['username']), - url_password=dict(type='str', default='', no_log=True, aliases=['password']), - force_basic_auth=dict(type='bool', default=True), - validate_certs=dict(type='bool', default=False), - certificate_authority_data=dict(type='str'), - insecure=dict(type='bool', default=False), - api_endpoint=dict(type='str', required=True), - patch_operation=dict(type='str', default='Strategic Merge Patch', aliases=['patch_strategy'], - choices=['JSON Patch', 'Merge Patch', 'Strategic Merge Patch']), - file_reference=dict(type='str'), - inline_data=dict(type='str'), - state=dict(type='str', default='present', choices=['absent', 'present', 'replace', 'update']) - ), - mutually_exclusive=(('file_reference', 'inline_data'), - ('url_username', 'insecure'), - ('url_password', 'insecure')), - required_one_of=(('file_reference', 'inline_data'),), - ) - - if not HAS_LIB_YAML: - module.fail_json(msg=missing_required_lib('PyYAML'), exception=YAML_IMP_ERR) - - decode_cert_data(module) - - api_endpoint = module.params.get('api_endpoint') - state = module.params.get('state') - insecure = module.params.get('insecure') - inline_data = module.params.get('inline_data') - file_reference = module.params.get('file_reference') - patch_operation = module.params.get('patch_operation') - - if inline_data: - if not isinstance(inline_data, dict) and not isinstance(inline_data, list): - data = yaml.safe_load(inline_data) - else: - data = inline_data - else: - try: - f = open(file_reference, "r") - data = [x for x in yaml.safe_load_all(f)] - f.close() - if not data: - module.fail_json(msg="No valid data could be found.") - except Exception: - module.fail_json(msg="The file '%s' was not found or contained invalid YAML/JSON data" % file_reference) - - # set the transport type and build the target endpoint url - transport = 'https' - if insecure: - transport = 'http' - - target_endpoint = "%s://%s" % (transport, api_endpoint) - - body = [] - changed = False - - # make sure the data is a list - if not isinstance(data, list): - data = [data] - - for item in data: - namespace = "default" - if item and 'metadata' in item: - namespace = item.get('metadata', {}).get('namespace', "default") - kind = item.get('kind', '').lower() - try: - url = target_endpoint + KIND_URL[kind] - except KeyError: - module.fail_json(msg="invalid resource kind specified in the data: '%s'" % kind) - url = url.replace("{namespace}", namespace) - else: - url = target_endpoint - - if state == 'present': - item_changed, item_body = k8s_create_resource(module, url, item) - elif state == 'absent': - item_changed, item_body = k8s_delete_resource(module, url, item) - elif state == 'replace': - item_changed, item_body = k8s_replace_resource(module, url, item) - elif state == 'update': - item_changed, item_body = k8s_update_resource(module, url, item, patch_operation) - - changed |= item_changed - body.append(item_body) - - module.exit_json(changed=changed, api_response=body) +from ansible.module_utils.common.removed import removed_module if __name__ == '__main__': - main() + removed_module(removed_in='2.9') diff --git a/lib/ansible/modules/clustering/openshift/_oc.py b/lib/ansible/modules/clustering/openshift/_oc.py index b5529d7b2cc..5fdcf542a85 100644 --- a/lib/ansible/modules/clustering/openshift/_oc.py +++ b/lib/ansible/modules/clustering/openshift/_oc.py @@ -9,457 +9,13 @@ __metaclass__ = type ANSIBLE_METADATA = { 'metadata_version': '1.1', - 'status': ['deprecated'], + 'status': ['removed'], 'supported_by': 'community' } -DOCUMENTATION = """ -author: - - "Kenneth D. Evensen (@kevensen)" -deprecated: - removed_in: "2.9" - why: This module used the oc command line tool, where as M(openshift_raw) goes over the REST API. - alternative: Use M(openshift_raw) instead. -description: - - This module allows management of resources in an OpenShift cluster. The - inventory host can be any host with network connectivity to the OpenShift - cluster; the default port being 8443/TCP. - - This module relies on a token to authenticate to OpenShift. This can either - be a user or a service account. -module: oc -options: - host: - description: - - "Hostname or address of the OpenShift API endpoint. By default, this is expected to be the current inventory host." - required: false - default: 127.0.0.1 - port: - description: - - "The port number of the API endpoint." - required: false - default: 8443 - inline: - description: - - "The inline definition of the resource. This is mutually exclusive with name, namespace and kind." - required: false - aliases: ['def', 'definition'] - kind: - description: The kind of the resource upon which to take action. - required: true - name: - description: - - "The name of the resource on which to take action." - required: false - namespace: - description: - - "The namespace of the resource upon which to take action." - required: false - token: - description: - - "The token with which to authenticate against the OpenShift cluster." - required: true - 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: yes - state: - choices: - - present - - absent - description: - - "If the state is present, and the resource doesn't exist, it shall be created using the inline definition. If the state is present and the - resource exists, the definition will be updated, again using an inline definition. If the state is absent, the resource will be deleted if it exists." - required: true -short_description: Manage OpenShift Resources -version_added: 2.4 - -""" - -EXAMPLES = """ -- name: Create project - oc: - state: present - inline: - kind: ProjectRequest - metadata: - name: ansibletestproject - displayName: Ansible Test Project - description: This project was created using Ansible - token: << redacted >> - -- name: Delete a service - oc: - state: absent - name: myservice - namespace: mynamespace - kind: Service - token: << redacted >> - -- name: Add project role Admin to a user - oc: - state: present - inline: - kind: RoleBinding - metadata: - name: admin - namespace: mynamespace - roleRef: - name: admin - userNames: - - "myuser" - token: << redacted >> - -- name: Obtain an object definition - oc: - state: present - name: myroute - namespace: mynamespace - kind: Route - token: << redacted >> -""" - -RETURN = ''' -result: - description: - The resource that was created, changed, or otherwise determined to be present. - In the case of a deletion, this is the response from the delete request. - returned: success - type: str -url: - description: The URL to the requested resource. - returned: success - type: str -method: - description: The HTTP method that was used to take action upon the resource - returned: success - type: str -... -''' - -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils import urls - - -class ApiEndpoint(object): - def __init__(self, host, port, api, version): - self.host = host - self.port = port - self.api = api - self.version = version - - def __str__(self): - url = "https://" - url += self.host - url += ":" - url += str(self.port) - url += "/" - url += self.api - url += "/" - url += self.version - return url - - -class ResourceEndpoint(ApiEndpoint): - def __init__(self, name, namespaced, api_endpoint): - super(ResourceEndpoint, self).__init__(api_endpoint.host, - api_endpoint.port, - api_endpoint.api, - api_endpoint.version) - self.name = name - self.namespaced = namespaced - - -class NamedResource(object): - def __init__(self, module, definition, resource_endpoint): - self.module = module - self.set_definition(definition) - self.resource_endpoint = resource_endpoint - - def name(self): - if 'name' in self.definition['metadata'].keys(): - return self.definition['metadata']['name'] - return None - - def namespace(self): - if 'namespace' in self.definition['metadata'].keys(): - return self.definition['metadata']['namespace'] - return None - - def set_definition(self, definition): - if isinstance(definition, str): - self.definition = self.module.from_json(definition) - else: - self.definition = definition - - def url(self, create=False): - url = str(self.resource_endpoint) - url += '/' - if self.resource_endpoint.namespaced: - url += 'namespaces/' - url += self.namespace() - url += '/' - url += self.resource_endpoint.name - if not create: - url += '/' - url += self.name() - return url - - def __dict__(self): - return self.definition - - def __str__(self): - return self.module.jsonify(self.definition) - - -class OC(object): - def __init__(self, module, token, host, port, - apis=None): - apis = ['api', 'oapi'] if apis is None else apis - - self.apis = apis - self.version = 'v1' - self.token = token - self.module = module - self.host = host - self.port = port - self.kinds = {} - - self.bearer = "Bearer " + self.token - self.headers = {"Authorization": self.bearer, - "Content-type": "application/json"} - # Build Endpoints - for api in self.apis: - endpoint = ApiEndpoint(self.host, - self.port, - api, - self.version) - # Create resource facts - response, code = self.connect(str(endpoint), "get") - - if code < 300: - self.build_kinds(response['resources'], endpoint) - - def build_kinds(self, resources, endpoint): - for resource in resources: - if 'generated' not in resource['name']: - self.kinds[resource['kind']] = \ - ResourceEndpoint(resource['name'].split('/')[0], - resource['namespaced'], - endpoint) - - def get(self, named_resource): - changed = False - response, code = self.connect(named_resource.url(), 'get') - return response, changed - - def exists(self, named_resource): - x, code = self.connect(named_resource.url(), 'get') - if code == 200: - return True - return False - - def delete(self, named_resource): - changed = False - response, code = self.connect(named_resource.url(), 'delete') - if code == 404: - return None, changed - elif code >= 300: - self.module.fail_json(msg='Failed to delete resource %s in \ - namespace %s with msg %s' - % (named_resource.name(), - named_resource.namespace(), - response)) - changed = True - return response, changed - - def create(self, named_resource): - changed = False - response, code = self.connect(named_resource.url(create=True), - 'post', - data=str(named_resource)) - if code == 404: - return None, changed - elif code == 409: - return self.get(named_resource) - elif code >= 300: - self.module.fail_json( - msg='Failed to create resource %s in \ - namespace %s with msg %s' % (named_resource.name(), - named_resource.namespace(), - response)) - changed = True - return response, changed - - def replace(self, named_resource, check_mode): - changed = False - - existing_definition, x = self.get(named_resource) - - new_definition, changed = self.merge(named_resource.definition, - existing_definition, - changed) - if changed and not check_mode: - named_resource.set_definition(new_definition) - response, code = self.connect(named_resource.url(), - 'put', - data=str(named_resource)) - - return response, changed - return existing_definition, changed - - def connect(self, url, method, data=None): - body = None - json_body = "" - if data is not None: - self.module.log(msg="Payload is %s" % data) - response, info = urls.fetch_url(module=self.module, - url=url, - headers=self.headers, - method=method, - data=data) - if response is not None: - body = response.read() - if info['status'] >= 300: - body = info['body'] - - message = "The URL, method, and code for connect is %s, %s, %d." % (url, method, info['status']) - if info['status'] == 401: - self.module.fail_json(msg=message + " Unauthorized. Check that you have a valid serivce account and token.") - - self.module.log(msg=message) - - try: - json_body = self.module.from_json(body) - except TypeError: - self.module.fail_json(msg="Response from %s expected to be a " + - "expected string or buffer." % url) - except ValueError: - return body, info['status'] - - return json_body, info['status'] - - def get_resource_endpoint(self, kind): - return self.kinds[kind] - - # Attempts to 'kindly' merge the dictionaries into a new object definition - def merge(self, source, destination, changed): - - for key, value in source.items(): - if isinstance(value, dict): - # get node or create one - try: - node = destination.setdefault(key, {}) - except AttributeError: - node = {} - finally: - x, changed = self.merge(value, node, changed) - - elif isinstance(value, list) and key in destination.keys(): - if destination[key] != source[key]: - destination[key] = source[key] - changed = True - - elif (key not in destination.keys() or - destination[key] != source[key]): - destination[key] = value - changed = True - return destination, changed - - -def main(): - - module = AnsibleModule( - argument_spec=dict( - host=dict(type='str', default='127.0.0.1'), - port=dict(type='int', default=8443), - definition=dict(aliases=['def', 'inline'], - type='dict'), - kind=dict(type='str'), - name=dict(type='str'), - namespace=dict(type='str'), - token=dict(required=True, type='str', no_log=True), - state=dict(required=True, - choices=['present', 'absent']), - validate_certs=dict(type='bool', default='yes') - ), - mutually_exclusive=(['kind', 'definition'], - ['name', 'definition'], - ['namespace', 'definition']), - required_if=([['state', 'absent', ['kind']]]), - required_one_of=([['kind', 'definition']]), - no_log=False, - supports_check_mode=True - ) - kind = None - definition = None - name = None - namespace = None - - host = module.params['host'] - port = module.params['port'] - definition = module.params['definition'] - state = module.params['state'] - kind = module.params['kind'] - name = module.params['name'] - namespace = module.params['namespace'] - token = module.params['token'] - - if definition is None: - definition = {} - definition['metadata'] = {} - definition['metadata']['name'] = name - definition['metadata']['namespace'] = namespace - - if "apiVersion" not in definition.keys(): - definition['apiVersion'] = 'v1' - if "kind" not in definition.keys(): - definition['kind'] = kind - - result = None - oc = OC(module, token, host, port) - resource = NamedResource(module, - definition, - oc.get_resource_endpoint(definition['kind'])) - - changed = False - method = '' - exists = oc.exists(resource) - module.log(msg="URL %s" % resource.url()) - - if state == 'present' and exists: - method = 'put' - result, changed = oc.replace(resource, module.check_mode) - elif state == 'present' and not exists and definition is not None: - method = 'create' - if not module.check_mode: - result, changed = oc.create(resource) - else: - changed = True - result = definition - elif state == 'absent' and exists: - method = 'delete' - if not module.check_mode: - result, changed = oc.delete(resource) - else: - changed = True - result = definition - - facts = {} - - if result is not None and "items" in result: - result['item_list'] = result.pop('items') - elif result is None and state == 'present': - result = 'Resource not present and no inline provided.' - facts['oc'] = {'definition': result, - 'url': resource.url(), - 'method': method} - - module.exit_json(changed=changed, ansible_facts=facts) +from ansible.module_utils.common.removed import removed_module if __name__ == '__main__': - main() + removed_module(removed_in='2.9') diff --git a/lib/ansible/modules/network/aos/_aos_asn_pool.py b/lib/ansible/modules/network/aos/_aos_asn_pool.py index c81fac21e7e..9b960f60121 100644 --- a/lib/ansible/modules/network/aos/_aos_asn_pool.py +++ b/lib/ansible/modules/network/aos/_aos_asn_pool.py @@ -1,346 +1,15 @@ #!/usr/bin/python # # (c) 2017 Apstra 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 . -# +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['deprecated'], + 'status': ['removed'], 'supported_by': 'community'} -DOCUMENTATION = ''' ---- -module: aos_asn_pool -author: Damien Garros (@dgarros) -version_added: "2.3" -short_description: Manage AOS ASN Pool -deprecated: - removed_in: "2.9" - why: This module does not support AOS 2.1 or later - alternative: See new modules at U(https://www.ansible.com/ansible-apstra). -description: - - Apstra AOS ASN Pool module let you manage your ASN Pool easily. You can create - and delete ASN Pool by Name, ID or by using a JSON File. This module - is idempotent and support the I(check) mode. It's using the AOS REST API. -requirements: - - "aos-pyez >= 0.6.0" -options: - session: - description: - - An existing AOS session as obtained by M(aos_login) module. - required: true - name: - description: - - Name of the ASN Pool to manage. - Only one of I(name), I(id) or I(content) can be set. - id: - description: - - AOS Id of the ASN Pool to manage. - Only one of I(name), I(id) or I(content) can be set. - content: - description: - - Datastructure of the ASN Pool to manage. The data can be in YAML / JSON or - directly a variable. It's the same datastructure that is returned - on success in I(value). - state: - description: - - Indicate what is the expected state of the ASN Pool (present or not). - default: present - choices: ['present', 'absent'] - ranges: - description: - - List of ASNs ranges to add to the ASN Pool. Each range must have 2 values. -''' - -EXAMPLES = ''' - -- name: "Create ASN Pool" - aos_asn_pool: - session: "{{ aos_session }}" - name: "my-asn-pool" - ranges: - - [ 100, 200 ] - state: present - register: asnpool - -- name: "Save ASN Pool into a file in JSON" - copy: - content: "{{ asnpool.value | to_nice_json }}" - dest: resources/asn_pool_saved.json - -- name: "Save ASN Pool into a file in YAML" - copy: - content: "{{ asnpool.value | to_nice_yaml }}" - dest: resources/asn_pool_saved.yaml - - -- name: "Delete ASN Pool" - aos_asn_pool: - session: "{{ aos_session }}" - name: "my-asn-pool" - state: absent - -- name: "Load ASN Pool from File(JSON)" - aos_asn_pool: - session: "{{ aos_session }}" - content: "{{ lookup('file', 'resources/asn_pool_saved.json') }}" - state: present - -- name: "Delete ASN Pool from File(JSON)" - aos_asn_pool: - session: "{{ aos_session }}" - content: "{{ lookup('file', 'resources/asn_pool_saved.json') }}" - state: absent - -- name: "Load ASN Pool from File(Yaml)" - aos_asn_pool: - session: "{{ aos_session }}" - content: "{{ lookup('file', 'resources/asn_pool_saved.yaml') }}" - state: present - register: test - -- name: "Delete ASN Pool from File(Yaml)" - aos_asn_pool: - session: "{{ aos_session }}" - content: "{{ lookup('file', 'resources/asn_pool_saved.yaml') }}" - state: absent -''' - -RETURNS = ''' -name: - description: Name of the ASN Pool - returned: always - type: str - sample: Private-ASN-pool - -id: - description: AOS unique ID assigned to the ASN Pool - returned: always - type: str - sample: fcc4ac1c-e249-4fe7-b458-2138bfb44c06 - -value: - description: Value of the object as returned by the AOS Server - returned: always - type: dict - sample: {'...'} -''' - -import json - -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.network.aos.aos import get_aos_session, find_collection_item, do_load_resource, check_aos_version, content_to_dict - - -def check_ranges_are_valid(module, ranges): - - i = 1 - for range in ranges: - if not isinstance(range, list): - module.fail_json(msg="Range (%i) must be a list not %s" % (i, type(range))) - elif len(range) != 2: - module.fail_json(msg="Range (%i) must be a list of 2 members, not %i" % (i, len(range))) - elif not isinstance(range[0], int): - module.fail_json(msg="1st element of range (%i) must be integer instead of %s " % (i, type(range[0]))) - elif not isinstance(range[1], int): - module.fail_json(msg="2nd element of range (%i) must be integer instead of %s " % (i, type(range[1]))) - elif range[1] <= range[0]: - module.fail_json(msg="2nd element of range (%i) must be bigger than 1st " % (i)) - - i += 1 - - return True - - -def get_list_of_range(asn_pool): - ranges = [] - - for range in asn_pool.value['ranges']: - ranges.append([range['first'], range['last']]) - - return ranges - - -def create_new_asn_pool(asn_pool, name, ranges): - - # Create value - datum = dict(display_name=name, ranges=[]) - for range in ranges: - datum['ranges'].append(dict(first=range[0], last=range[1])) - - asn_pool.datum = datum - - # Write to AOS - return asn_pool.write() - - -def asn_pool_absent(module, aos, my_pool): - - margs = module.params - - # If the module do not exist, return directly - if my_pool.exists is False: - module.exit_json(changed=False, name=margs['name'], id='', value={}) - - # Check if object is currently in Use or Not - # If in Use, return an error - if my_pool.value: - if my_pool.value['status'] != 'not_in_use': - module.fail_json(msg="Unable to delete ASN Pool '%s' is currently in use" % my_pool.name) - else: - module.fail_json(msg="ASN Pool object has an invalid format, value['status'] must be defined") - - # If not in check mode, delete Ip Pool - if not module.check_mode: - try: - my_pool.delete() - except Exception: - module.fail_json(msg="An error occurred, while trying to delete the ASN Pool") - - module.exit_json(changed=True, - name=my_pool.name, - id=my_pool.id, - value={}) - - -def asn_pool_present(module, aos, my_pool): - - margs = module.params - - # if content is defined, create object from Content - if margs['content'] is not None: - - if 'display_name' in module.params['content'].keys(): - do_load_resource(module, aos.AsnPools, module.params['content']['display_name']) - else: - module.fail_json(msg="Unable to find display_name in 'content', Mandatory") - - # if asn_pool doesn't exist already, create a new one - if my_pool.exists is False and 'name' not in margs.keys(): - module.fail_json(msg="name is mandatory for module that don't exist currently") - - elif my_pool.exists is False: - - if not module.check_mode: - try: - my_new_pool = create_new_asn_pool(my_pool, margs['name'], margs['ranges']) - my_pool = my_new_pool - except Exception: - module.fail_json(msg="An error occurred while trying to create a new ASN Pool ") - - module.exit_json(changed=True, - name=my_pool.name, - id=my_pool.id, - value=my_pool.value) - - # Currently only check if the pool exist or not - # if exist return change false - # - # Later it would be good to check if the list of ASN are same - # if pool already exist, check if list of ASN is the same - # if same just return the object and report change false - # if set(get_list_of_range(my_pool)) == set(margs['ranges']): - module.exit_json(changed=False, - name=my_pool.name, - id=my_pool.id, - value=my_pool.value) - -# ######################################################## -# Main Function -# ######################################################## - - -def asn_pool(module): - - margs = module.params - - try: - aos = get_aos_session(module, margs['session']) - except Exception: - module.fail_json(msg="Unable to login to the AOS server") - - item_name = False - item_id = False - - # Check ID / Name and Content - if margs['content'] is not None: - - content = content_to_dict(module, margs['content']) - - if 'display_name' in content.keys(): - item_name = content['display_name'] - else: - module.fail_json(msg="Unable to extract 'display_name' from 'content'") - - elif margs['name'] is not None: - item_name = margs['name'] - - elif margs['id'] is not None: - item_id = margs['id'] - - # If ranges are provided, check if they are valid - if 'ranges' in margs.keys(): - check_ranges_are_valid(module, margs['ranges']) - - # ---------------------------------------------------- - # Find Object if available based on ID or Name - # ---------------------------------------------------- - try: - my_pool = find_collection_item(aos.AsnPools, - item_name=item_name, - item_id=item_id) - except Exception: - module.fail_json(msg="Unable to find the IP Pool based on name or ID, something went wrong") - - # ---------------------------------------------------- - # Proceed based on State value - # ---------------------------------------------------- - if margs['state'] == 'absent': - - asn_pool_absent(module, aos, my_pool) - - elif margs['state'] == 'present': - - asn_pool_present(module, aos, my_pool) - - -def main(): - module = AnsibleModule( - argument_spec=dict( - session=dict(required=True, type="dict"), - name=dict(required=False), - id=dict(required=False), - content=dict(required=False, type="json"), - state=dict(required=False, - choices=['present', 'absent'], - default="present"), - ranges=dict(required=False, type="list", default=[]) - ), - mutually_exclusive=[('name', 'id', 'content')], - required_one_of=[('name', 'id', 'content')], - supports_check_mode=True - ) - - # Check if aos-pyez is present and match the minimum version - check_aos_version(module, '0.6.0') - - asn_pool(module) +from ansible.module_utils.common.removed import removed_module -if __name__ == "__main__": - main() +if __name__ == '__main__': + removed_module(removed_in='2.9') diff --git a/lib/ansible/modules/network/aos/_aos_blueprint.py b/lib/ansible/modules/network/aos/_aos_blueprint.py index 25bd8687573..9b960f60121 100644 --- a/lib/ansible/modules/network/aos/_aos_blueprint.py +++ b/lib/ansible/modules/network/aos/_aos_blueprint.py @@ -1,300 +1,15 @@ #!/usr/bin/python # # (c) 2017 Apstra 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 . -# +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['deprecated'], + 'status': ['removed'], 'supported_by': 'community'} -DOCUMENTATION = ''' ---- -module: aos_blueprint -author: jeremy@apstra.com (@jeremyschulman) -version_added: "2.3" -short_description: Manage AOS blueprint instance -deprecated: - removed_in: "2.9" - why: This module does not support AOS 2.1 or later - alternative: See new modules at U(https://www.ansible.com/ansible-apstra). -description: - - Apstra AOS Blueprint module let you manage your Blueprint easily. You can create - create and delete Blueprint by Name or ID. You can also use it to retrieve - all data from a blueprint. This module is idempotent - and support the I(check) mode. It's using the AOS REST API. -requirements: - - "aos-pyez >= 0.6.0" -options: - session: - description: - - An existing AOS session as obtained by M(aos_login) module. - required: true - name: - description: - - Name of the Blueprint to manage. - Only one of I(name) or I(id) can be set. - id: - description: - - AOS Id of the IP Pool to manage (can't be used to create a new IP Pool). - Only one of I(name) or I(id) can be set. - state: - description: - - Indicate what is the expected state of the Blueprint. - choices: ['present', 'absent', 'build-ready'] - default: present - timeout: - description: - - When I(state=build-ready), this timeout identifies timeout in seconds to wait before - declaring a failure. - default: 5 - template: - description: - - When creating a blueprint, this value identifies, by name, an existing engineering - design template within the AOS-server. - reference_arch: - description: - - When creating a blueprint, this value identifies a known AOS reference - architecture value. I(Refer to AOS-server documentation for available values). -''' - -EXAMPLES = ''' -- name: Creating blueprint - aos_blueprint: - session: "{{ aos_session }}" - name: "my-blueprint" - template: "my-template" - reference_arch: two_stage_l3clos - state: present - -- name: Access a blueprint and get content - aos_blueprint: - session: "{{ aos_session }}" - name: "{{ blueprint_name }}" - template: "{{ blueprint_template }}" - state: present - register: bp - -- name: Delete a blueprint - aos_blueprint: - session: "{{ aos_session }}" - name: "my-blueprint" - state: absent - -- name: Await blueprint build-ready, and obtain contents - aos_blueprint: - session: "{{ aos_session }}" - name: "{{ blueprint_name }}" - state: build-ready - register: bp -''' - -RETURNS = ''' -name: - description: Name of the Blueprint - returned: always - type: str - sample: My-Blueprint - -id: - description: AOS unique ID assigned to the Blueprint - returned: always - type: str - sample: fcc4ac1c-e249-4fe7-b458-2138bfb44c06 - -value: - description: Information about the Blueprint - returned: always - type: dict - sample: {'...'} - -contents: - description: Blueprint contents data-dictionary - returned: always - type: dict - sample: { ... } - -build_errors: - description: When state='build-ready', and build errors exist, this contains list of errors - returned: only when build-ready returns fail - type: list - sample: [{...}, {...}] -''' - -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.network.aos.aos import get_aos_session, check_aos_version, find_collection_item - - -def create_blueprint(module, aos, name): - - margs = module.params - - try: - - template_id = aos.DesignTemplates[margs['template']].id - - # Create a new Object based on the name - blueprint = aos.Blueprints[name] - blueprint.create(template_id, reference_arch=margs['reference_arch']) - - except Exception as exc: - msg = "Unable to create blueprint: %s" % exc.message - if 'UNPROCESSABLE ENTITY' in exc.message: - msg += ' (likely missing dependencies)' - - module.fail_json(msg=msg) - - return blueprint - - -def ensure_absent(module, aos, blueprint): - - if blueprint.exists is False: - module.exit_json(changed=False) - - else: - - if not module.check_mode: - try: - blueprint.delete() - except Exception as exc: - module.fail_json(msg='Unable to delete blueprint, %s' % exc.message) - - module.exit_json(changed=True, - id=blueprint.id, - name=blueprint.name) - - -def ensure_present(module, aos, blueprint): - margs = module.params - - if blueprint.exists: - module.exit_json(changed=False, - id=blueprint.id, - name=blueprint.name, - value=blueprint.value, - contents=blueprint.contents) - - else: - - # Check if template is defined and is valid - if margs['template'] is None: - module.fail_json(msg="You must define a 'template' name to create a new blueprint, currently missing") - - elif aos.DesignTemplates.find(label=margs['template']) is None: - module.fail_json(msg="You must define a Valid 'template' name to create a new blueprint, %s is not valid" % margs['template']) - - # Check if reference_arch - if margs['reference_arch'] is None: - module.fail_json(msg="You must define a 'reference_arch' to create a new blueprint, currently missing") - - if not module.check_mode: - blueprint = create_blueprint(module, aos, margs['name']) - module.exit_json(changed=True, - id=blueprint.id, - name=blueprint.name, - value=blueprint.value, - contents=blueprint.contents) - else: - module.exit_json(changed=True, - name=margs['name']) - - -def ensure_build_ready(module, aos, blueprint): - margs = module.params - - if not blueprint.exists: - module.fail_json(msg='blueprint %s does not exist' % blueprint.name) - - if blueprint.await_build_ready(timeout=margs['timeout'] * 1000): - module.exit_json(contents=blueprint.contents) - else: - module.fail_json(msg='blueprint %s has build errors', - build_erros=blueprint.build_errors) - - -def aos_blueprint(module): - - margs = module.params - - try: - aos = get_aos_session(module, margs['session']) - except Exception: - module.fail_json(msg="Unable to login to the AOS server") - - item_name = False - item_id = False - - if margs['name'] is not None: - item_name = margs['name'] - - elif margs['id'] is not None: - item_id = margs['id'] - - # ---------------------------------------------------- - # Find Object if available based on ID or Name - # ---------------------------------------------------- - try: - my_blueprint = find_collection_item(aos.Blueprints, - item_name=item_name, - item_id=item_id) - except Exception: - module.fail_json(msg="Unable to find the Blueprint based on name or ID, something went wrong") - - # ---------------------------------------------------- - # Proceed based on State value - # ---------------------------------------------------- - if margs['state'] == 'absent': - - ensure_absent(module, aos, my_blueprint) - - elif margs['state'] == 'present': - - ensure_present(module, aos, my_blueprint) - - elif margs['state'] == 'build-ready': - - ensure_build_ready(module, aos, my_blueprint) - - -def main(): - module = AnsibleModule( - argument_spec=dict( - session=dict(required=True, type="dict"), - name=dict(required=False), - id=dict(required=False), - state=dict(choices=[ - 'present', 'absent', 'build-ready'], - default='present'), - timeout=dict(type="int", default=5), - template=dict(required=False), - reference_arch=dict(required=False) - ), - mutually_exclusive=[('name', 'id')], - required_one_of=[('name', 'id')], - supports_check_mode=True - ) - - # Check if aos-pyez is present and match the minimum version - check_aos_version(module, '0.6.0') - - aos_blueprint(module) +from ansible.module_utils.common.removed import removed_module if __name__ == '__main__': - main() + removed_module(removed_in='2.9') diff --git a/lib/ansible/modules/network/aos/_aos_blueprint_param.py b/lib/ansible/modules/network/aos/_aos_blueprint_param.py index e1962d16922..9b960f60121 100644 --- a/lib/ansible/modules/network/aos/_aos_blueprint_param.py +++ b/lib/ansible/modules/network/aos/_aos_blueprint_param.py @@ -1,385 +1,15 @@ #!/usr/bin/python # # (c) 2017 Apstra 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 . -# +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['deprecated'], + 'status': ['removed'], 'supported_by': 'community'} -DOCUMENTATION = ''' ---- -module: aos_blueprint_param -author: jeremy@apstra.com (@jeremyschulman) -version_added: "2.3" -short_description: Manage AOS blueprint parameter values -deprecated: - removed_in: "2.9" - why: This module does not support AOS 2.1 or later - alternative: See new modules at U(https://www.ansible.com/ansible-apstra). -description: - - Apstra AOS Blueprint Parameter module let you manage your Blueprint Parameter easily. - You can create access, define and delete Blueprint Parameter. The list of - Parameters supported is different per Blueprint. The option I(get_param_list) - can help you to access the list of supported Parameters for your blueprint. - This module is idempotent and support the I(check) mode. It's using the AOS REST API. -requirements: - - "aos-pyez >= 0.6.0" -options: - session: - description: - - An existing AOS session as obtained by M(aos_login) module. - required: true - blueprint: - description: - - Blueprint Name or Id as defined in AOS. - required: True - name: - description: - - Name of blueprint parameter, as defined by AOS design template. You can - use the option I(get_param_list) to get the complete list of supported - parameters for your blueprint. - value: - description: - - Blueprint parameter value. This value may be transformed by using the - I(param_map) field; used when the blueprint parameter requires - an AOS unique ID value. - get_param_list: - description: - - Get the complete list of supported parameters for this blueprint and the - description of those parameters. - state: - description: - - Indicate what is the expected state of the Blueprint Parameter (present or not). - default: present - choices: ['present', 'absent'] - param_map: - description: - - Defines the aos-pyez collection that will is used to map the user-defined - item name into the AOS unique ID value. For example, if the caller - provides an IP address pool I(param_value) called "Server-IpAddrs", then - the aos-pyez collection is 'IpPools'. Some I(param_map) are already defined - by default like I(logical_device_maps). -''' - -EXAMPLES = ''' - -- name: Add Logical Device Maps information in a Blueprint - aos_blueprint_param: - session: "{{ aos_session }}" - blueprint: "my-blueprint-l2" - name: "logical_device_maps" - value: - spine_1: CumulusVX-Spine-Switch - spine_2: CumulusVX-Spine-Switch - leaf_1: CumulusVX-Leaf-Switch - leaf_2: CumulusVX-Leaf-Switch - leaf_3: CumulusVX-Leaf-Switch - state: present - -- name: Access Logical Device Maps information from a Blueprint - aos_blueprint_param: - session: "{{ aos_session }}" - blueprint: "my-blueprint-l2" - name: "logical_device_maps" - state: present - -- name: Reset Logical Device Maps information in a Blueprint - aos_blueprint_param: - session: "{{ aos_session }}" - blueprint: "my-blueprint-l2" - name: "logical_device_maps" - state: absent - -- name: Get list of all supported Params for a blueprint - aos_blueprint_param: - session: "{{ aos_session }}" - blueprint: "my-blueprint-l2" - get_param_list: yes - register: params_list -- debug: var=params_list - -- name: Add Resource Pools information in a Blueprint, by providing a param_map - aos_blueprint_param: - session: "{{ aos_session }}" - blueprint: "my-blueprint-l2" - name: "resource_pools" - value: - leaf_loopback_ips: ['Switches-IpAddrs'] - spine_loopback_ips: ['Switches-IpAddrs'] - spine_leaf_link_ips: ['Switches-IpAddrs'] - spine_asns: ['Private-ASN-pool'] - leaf_asns: ['Private-ASN-pool'] - virtual_network_svi_subnets: ['Servers-IpAddrs'] - param_map: - leaf_loopback_ips: IpPools - spine_loopback_ips: IpPools - spine_leaf_link_ips: IpPools - spine_asns: AsnPools - leaf_asns: AsnPools - virtual_network_svi_subnets: IpPools - state: present -''' - -RETURNS = ''' -blueprint: - description: Name of the Blueprint - returned: always - type: str - sample: Server-IpAddrs - -name: - description: Name of the Blueprint Parameter - returned: always - type: str - sample: fcc4ac1c-e249-4fe7-b458-2138bfb44c06 - -value: - description: Value of the Blueprint Parameter as returned by the AOS Server - returned: always - type: dict - sample: {'...'} - -params_list: - description: Value of the Blueprint Parameter as returned by the AOS Server - returned: when I(get_param_list) is defined. - type: dict - sample: {'...'} -''' - -import json - -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.network.aos.aos import get_aos_session, find_collection_item, check_aos_version -from ansible.module_utils._text import to_native - -try: - import yaml - HAS_YAML = True -except ImportError: - HAS_YAML = False - -try: - from apstra.aosom.collection_mapper import CollectionMapper, MultiCollectionMapper - HAS_AOS_PYEZ_MAPPER = True -except ImportError: - HAS_AOS_PYEZ_MAPPER = False - -param_map_list = dict( - logical_device_maps='LogicalDeviceMaps', - resource_pools=dict( - spine_asns="AsnPools", - leaf_asns="AsnPools", - virtual_network_svi_subnets="IpPools", - spine_loopback_ips="IpPools", - leaf_loopback_ips="IpPools", - spine_leaf_link_ips="IpPools" - ) -) - - -def get_collection_from_param_map(module, aos): - - param_map = None - - # Check if param_map is provided - if module.params['param_map'] is not None: - param_map_json = module.params['param_map'] - - if not HAS_YAML: - module.fail_json(msg="Python library Yaml is mandatory to use 'param_map'") - - try: - param_map = yaml.safe_load(param_map_json) - except Exception: - module.fail_json(msg="Unable to parse param_map information") - - else: - # search in the param_map_list to find the right one - for key, value in param_map_list.items(): - if module.params['name'] == key: - param_map = value - - # If param_map is defined, search for a Collection that matches - if param_map: - if isinstance(param_map, dict): - return MultiCollectionMapper(aos, param_map) - else: - return CollectionMapper(getattr(aos, param_map)) - - return None - - -def blueprint_param_present(module, aos, blueprint, param, param_value): - - margs = module.params - - # If param_value is not defined, just return the object - if not param_value: - module.exit_json(changed=False, - blueprint=blueprint.name, - name=param.name, - value=param.value) - - # Check if current value is the same or not - elif param.value != param_value: - if not module.check_mode: - try: - param.value = param_value - except Exception as exc: - module.fail_json(msg='unable to write to param %s: %s' % - (margs['name'], to_native(exc))) - - module.exit_json(changed=True, - blueprint=blueprint.name, - name=param.name, - value=param.value) - - # If value are already the same, nothing needs to be changed - else: - module.exit_json(changed=False, - blueprint=blueprint.name, - name=param.name, - value=param.value) - - -def blueprint_param_absent(module, aos, blueprint, param, param_value): - - margs = module.params - - # Check if current value is the same or not - if param.value != dict(): - if not module.check_mode: - try: - param.value = {} - except Exception as exc: - module.fail_json(msg='Unable to write to param %s: %s' % (margs['name'], to_native(exc))) - - module.exit_json(changed=True, - blueprint=blueprint.name, - name=param.name, - value=param.value) - - else: - module.exit_json(changed=False, - blueprint=blueprint.name, - name=param.name, - value=param.value) - - -def blueprint_param(module): - - margs = module.params - - # -------------------------------------------------------------------- - # Get AOS session object based on Session Info - # -------------------------------------------------------------------- - try: - aos = get_aos_session(module, margs['session']) - except Exception: - module.fail_json(msg="Unable to login to the AOS server") - - # -------------------------------------------------------------------- - # Get the blueprint Object based on either name or ID - # -------------------------------------------------------------------- - try: - blueprint = find_collection_item(aos.Blueprints, - item_name=margs['blueprint'], - item_id=margs['blueprint']) - except Exception: - module.fail_json(msg="Unable to find the Blueprint based on name or ID, something went wrong") - - if blueprint.exists is False: - module.fail_json(msg='Blueprint %s does not exist.\n' - 'known blueprints are [%s]' % - (margs['blueprint'], ','.join(aos.Blueprints.names))) - - # -------------------------------------------------------------------- - # If get_param_list is defined, build the list of supported parameters - # and extract info for each - # -------------------------------------------------------------------- - if margs['get_param_list']: - - params_list = {} - for param in blueprint.params.names: - params_list[param] = blueprint.params[param].info - - module.exit_json(changed=False, - blueprint=blueprint.name, - params_list=params_list) - - # -------------------------------------------------------------------- - # Check Param name, return an error if not supported by this blueprint - # -------------------------------------------------------------------- - if margs['name'] in blueprint.params.names: - param = blueprint.params[margs['name']] - else: - module.fail_json(msg='unable to access param %s' % margs['name']) - - # -------------------------------------------------------------------- - # Check if param_value needs to be converted to an object - # based on param_map - # -------------------------------------------------------------------- - param_value = margs['value'] - param_collection = get_collection_from_param_map(module, aos) - - # If a collection is find and param_value is defined, - # convert param_value into an object - if param_collection and param_value: - param_value = param_collection.from_label(param_value) - - # -------------------------------------------------------------------- - # Proceed based on State value - # -------------------------------------------------------------------- - if margs['state'] == 'absent': - - blueprint_param_absent(module, aos, blueprint, param, param_value) - - elif margs['state'] == 'present': - - blueprint_param_present(module, aos, blueprint, param, param_value) - - -def main(): - module = AnsibleModule( - argument_spec=dict( - session=dict(required=True, type="dict"), - blueprint=dict(required=True), - get_param_list=dict(required=False, type="bool"), - name=dict(required=False), - value=dict(required=False, type="dict"), - param_map=dict(required=False), - state=dict(choices=['present', 'absent'], default='present') - ), - supports_check_mode=True - ) - - # Check if aos-pyez is present and match the minimum version - check_aos_version(module, '0.6.0') - - # aos-pyez availability has been verify already by "check_aos_version" - # but this module requires few more object - if not HAS_AOS_PYEZ_MAPPER: - module.fail_json(msg='unable to load the Mapper library from aos-pyez') - - blueprint_param(module) +from ansible.module_utils.common.removed import removed_module if __name__ == '__main__': - main() + removed_module(removed_in='2.9') diff --git a/lib/ansible/modules/network/aos/_aos_blueprint_virtnet.py b/lib/ansible/modules/network/aos/_aos_blueprint_virtnet.py index 8814b26f46e..9b960f60121 100644 --- a/lib/ansible/modules/network/aos/_aos_blueprint_virtnet.py +++ b/lib/ansible/modules/network/aos/_aos_blueprint_virtnet.py @@ -1,221 +1,15 @@ #!/usr/bin/python # # (c) 2017 Apstra 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 . -# +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['deprecated'], + 'status': ['removed'], 'supported_by': 'community'} -DOCUMENTATION = ''' ---- -module: aos_blueprint_virtnet -author: Damien Garros (@dgarros) -version_added: "2.3" -short_description: Manage AOS blueprint parameter values -deprecated: - removed_in: "2.9" - why: This module does not support AOS 2.1 or later - alternative: See new modules at U(https://www.ansible.com/ansible-apstra). -description: - - Apstra AOS Blueprint Virtual Network module let you manage your Virtual Network easily. - You can create access, define and delete Virtual Network by name or by using a JSON / Yaml file. - This module is idempotent and support the I(check) mode. It's using the AOS REST API. -requirements: - - "aos-pyez >= 0.6.0" -options: - session: - description: - - An existing AOS session as obtained by M(aos_login) module. - required: true - blueprint: - description: - - Blueprint Name or Id as defined in AOS. - required: True - name: - description: - - Name of Virtual Network as part of the Blueprint. - content: - description: - - Datastructure of the Virtual Network to manage. The data can be in YAML / JSON or - directly a variable. It's the same datastructure that is returned on success in I(value). - state: - description: - - Indicate what is the expected state of the Virtual Network (present or not). - default: present - choices: ['present', 'absent'] -''' - -EXAMPLES = ''' - -- name: "Access Existing Virtual Network" - aos_blueprint_virtnet: - session: "{{ aos_session }}" - blueprint: "my-blueprint-l2" - name: "my-virtual-network" - state: present - -- name: "Delete Virtual Network with JSON File" - aos_blueprint_virtnet: - session: "{{ aos_session }}" - blueprint: "my-blueprint-l2" - content: "{{ lookup('file', 'resources/virtual-network-02.json') }}" - state: absent - -- name: "Create Virtual Network" - aos_blueprint_virtnet: - session: "{{ aos_session }}" - blueprint: "my-blueprint-l2" - content: "{{ lookup('file', 'resources/virtual-network-02.json') }}" - state: present -''' - -import json - -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils._text import to_native -from ansible.module_utils.network.aos.aos import get_aos_session, find_collection_item, do_load_resource, check_aos_version, content_to_dict - - -def ensure_present(module, aos, blueprint, virtnet): - - # if exist already return tru - if virtnet.exists: - module.exit_json(changed=False, - blueprint=blueprint.name, - name=virtnet.name, - id=virtnet.id, - value=virtnet.value) - - else: - if not module.check_mode: - try: - virtnet.create(module.params['content']) - except Exception as e: - module.fail_json(msg="unable to create virtual-network : %s" % to_native(e)) - - module.exit_json(changed=True, - blueprint=blueprint.name, - name=virtnet.name, - id=virtnet.id, - value=virtnet.value) - - -def ensure_absent(module, aos, blueprint, virtnet): - - if virtnet.exists: - if not module.check_mode: - try: - virtnet.delete() - except Exception as e: - module.fail_json(msg="unable to delete virtual-network %s : %s" % (virtnet.name, to_native(e))) - - module.exit_json(changed=True, - blueprint=blueprint.name) - - else: - module.exit_json(changed=False, - blueprint=blueprint.name) - - -def blueprint_virtnet(module): - - margs = module.params - - # -------------------------------------------------------------------- - # Get AOS session object based on Session Info - # -------------------------------------------------------------------- - try: - aos = get_aos_session(module, margs['session']) - except Exception: - module.fail_json(msg="Unable to login to the AOS server") - - # -------------------------------------------------------------------- - # Get the blueprint Object based on either name or ID - # -------------------------------------------------------------------- - try: - blueprint = find_collection_item(aos.Blueprints, - item_name=margs['blueprint'], - item_id=margs['blueprint']) - except Exception: - module.fail_json(msg="Unable to find the Blueprint based on name or ID, something went wrong") - - if blueprint.exists is False: - module.fail_json(msg='Blueprint %s does not exist.\n' - 'known blueprints are [%s]' % - (margs['blueprint'], ','.join(aos.Blueprints.names))) - - # -------------------------------------------------------------------- - # Convert "content" to dict and extract name - # -------------------------------------------------------------------- - if margs['content'] is not None: - - content = content_to_dict(module, margs['content']) - - if 'display_name' in content.keys(): - item_name = content['display_name'] - else: - module.fail_json(msg="Unable to extract 'display_name' from 'content'") - - elif margs['name'] is not None: - item_name = margs['name'] - - # -------------------------------------------------------------------- - # Try to find VirtualNetwork object - # -------------------------------------------------------------------- - try: - virtnet = blueprint.VirtualNetworks[item_name] - except Exception: - module.fail_json(msg="Something went wrong while trying to find Virtual Network %s in blueprint %s" - % (item_name, blueprint.name)) - - # -------------------------------------------------------------------- - # Proceed based on State value - # -------------------------------------------------------------------- - if margs['state'] == 'absent': - - ensure_absent(module, aos, blueprint, virtnet) - - elif margs['state'] == 'present': - - ensure_present(module, aos, blueprint, virtnet) - - -def main(): - module = AnsibleModule( - argument_spec=dict( - session=dict(required=True, type="dict"), - blueprint=dict(required=True), - name=dict(required=False), - content=dict(required=False, type="json"), - state=dict(choices=['present', 'absent'], default='present') - ), - mutually_exclusive=[('name', 'content')], - required_one_of=[('name', 'content')], - supports_check_mode=True - ) - - # Check if aos-pyez is present and match the minimum version - check_aos_version(module, '0.6.0') - - blueprint_virtnet(module) +from ansible.module_utils.common.removed import removed_module if __name__ == '__main__': - main() + removed_module(removed_in='2.9') diff --git a/lib/ansible/modules/network/aos/_aos_device.py b/lib/ansible/modules/network/aos/_aos_device.py index f726c1f060b..9b960f60121 100644 --- a/lib/ansible/modules/network/aos/_aos_device.py +++ b/lib/ansible/modules/network/aos/_aos_device.py @@ -1,222 +1,15 @@ #!/usr/bin/python # # (c) 2017 Apstra 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 . -# +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['deprecated'], + 'status': ['removed'], 'supported_by': 'community'} -DOCUMENTATION = ''' ---- -module: aos_device -author: Damien Garros (@dgarros) -version_added: "2.3" -short_description: Manage Devices on AOS Server -deprecated: - removed_in: "2.9" - why: This module does not support AOS 2.1 or later - alternative: See new modules at U(https://www.ansible.com/ansible-apstra). -description: - - Apstra AOS Device module let you manage your devices in AOS easily. You can - approve devices and define in which state the device should be. Currently - only the state I(normal) is supported but the goal is to extend this module - with additional state. This module is idempotent and support the I(check) mode. - It's using the AOS REST API. -requirements: - - "aos-pyez >= 0.6.0" -options: - session: - description: - - An existing AOS session as obtained by M(aos_login) module. - required: true - name: - description: - - The device serial-number; i.e. uniquely identifies the device in the - AOS system. Only one of I(name) or I(id) can be set. - id: - description: - - The AOS internal id for a device; i.e. uniquely identifies the device in the - AOS system. Only one of I(name) or I(id) can be set. - state: - description: - - Define in which state the device should be. Currently only I(normal) - is supported but the goal is to add I(maint) and I(decomm). - default: normal - choices: ['normal'] - approve: - description: - - The approve argument instruct the module to convert a device in quarantine - mode into approved mode. - default: "no" - type: bool - location: - description: - - When approving a device using the I(approve) argument, it's possible - define the location of the device. -''' - -EXAMPLES = ''' - -- name: Approve a new device - aos_device: - session: "{{ aos_session }}" - name: D2060B2F105429GDABCD123 - state: 'normal' - approve: true - location: "rack-45, ru-18" -''' - - -RETURNS = ''' -name: - description: Name of the Device, usually the serial-number. - returned: always - type: str - sample: Server-IpAddrs - -id: - description: AOS unique ID assigned to the Device - returned: always - type: str - sample: fcc4ac1c-e249-4fe7-b458-2138bfb44c06 - -value: - description: Value of the object as returned by the AOS Server - returned: always - type: dict - sample: {'...'} -''' - -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.network.aos.aos import HAS_AOS_PYEZ, get_aos_session, check_aos_version, find_collection_item - -if HAS_AOS_PYEZ: - from apstra.aosom.exc import SessionError, SessionRqstError - - -def aos_device_normal(module, aos, dev): - - margs = module.params - - # If approve is define, check if the device needs to be approved or not - if margs['approve'] is not None: - - if dev.is_approved: - module.exit_json(changed=False, - name=dev.name, - id=dev.id, - value=dev.value) - - if not module.check_mode: - try: - dev.approve(location=margs['location']) - except (SessionError, SessionRqstError): - module.fail_json(msg="Unable to approve device")\ - - module.exit_json(changed=True, - name=dev.name, - id=dev.id, - value=dev.value) - else: - # Check if the device is online - if dev.state in ('OOS-READY', 'IS-READY'): - module.exit_json(changed=False, - name=dev.name, - id=dev.id, - value=dev.value) - else: - module.fail_json(msg="Device is in '%s' state" % dev.state) - - -def aos_device(module): - margs = module.params - - try: - aos = get_aos_session(module, margs['session']) - except Exception: - module.fail_json(msg="Unable to login to the AOS server") - - item_name = False - item_id = False - - if margs['id'] is not None: - item_id = margs['id'] - - elif margs['name'] is not None: - item_name = margs['name'] - - # ---------------------------------------------------- - # Find Object if available based on ID or Name - # ---------------------------------------------------- - dev = find_collection_item(aos.Devices, - item_name=item_name, - item_id=item_id) - - if dev.exists is False: - module.fail_json(msg="unknown device '%s'" % margs['name']) - - # ---------------------------------------------------- - # Valid device state for reference - # ---------------------------------------------------- - # DEVICE_STATE_IS_ACTIVE = 1; - # DEVICE_STATE_IS_READY = 2; - # DEVICE_STATE_IS_NOCOMMS = 3; - # DEVICE_STATE_IS_MAINT = 4; - # DEVICE_STATE_IS_REBOOTING = 5; - # DEVICE_STATE_OOS_STOCKED = 6; - # DEVICE_STATE_OOS_QUARANTINED = 7; - # DEVICE_STATE_OOS_READY = 8; - # DEVICE_STATE_OOS_NOCOMMS = 9; - # DEVICE_STATE_OOS_DECOMM = 10; - # DEVICE_STATE_OOS_MAINT = 11; - # DEVICE_STATE_OOS_REBOOTING = 12; - # DEVICE_STATE_ERROR = 13; - # ---------------------------------------------------- - # State == Normal - # ---------------------------------------------------- - if margs['state'] == 'normal': - aos_device_normal(module, aos, dev) - - -def main(): - - module = AnsibleModule( - argument_spec=dict( - session=dict(required=True, type="dict"), - name=dict(required=False), - id=dict(required=False), - state=dict(choices=['normal'], - default='normal'), - approve=dict(required=False, type='bool'), - location=dict(required=False, default='') - ), - mutually_exclusive=[('name', 'id')], - required_one_of=[('name', 'id')], - supports_check_mode=True - ) - - # Check if aos-pyez is present and match the minimum version - check_aos_version(module, '0.6.0') - - aos_device(module) +from ansible.module_utils.common.removed import removed_module -if __name__ == "__main__": - main() +if __name__ == '__main__': + removed_module(removed_in='2.9') diff --git a/lib/ansible/modules/network/aos/_aos_external_router.py b/lib/ansible/modules/network/aos/_aos_external_router.py index 32a7539a1dc..9b960f60121 100644 --- a/lib/ansible/modules/network/aos/_aos_external_router.py +++ b/lib/ansible/modules/network/aos/_aos_external_router.py @@ -1,342 +1,15 @@ #!/usr/bin/python # # (c) 2017 Apstra 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 . -# +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['deprecated'], + 'status': ['removed'], 'supported_by': 'community'} -DOCUMENTATION = ''' ---- -module: aos_external_router -author: Damien Garros (@dgarros) -version_added: "2.3" -short_description: Manage AOS External Router -deprecated: - removed_in: "2.9" - why: This module does not support AOS 2.1 or later - alternative: See new modules at U(https://www.ansible.com/ansible-apstra). -description: - - Apstra AOS External Router module let you manage your External Router easily. You can create - create and delete External Router by Name, ID or by using a JSON File. This module - is idempotent and support the I(check) mode. It's using the AOS REST API. -requirements: - - "aos-pyez >= 0.6.0" -options: - session: - description: - - An existing AOS session as obtained by M(aos_login) module. - required: true - name: - description: - - Name of the External Router to manage. - Only one of I(name), I(id) or I(content) can be set. - id: - description: - - AOS Id of the External Router to manage (can't be used to create a new External Router), - Only one of I(name), I(id) or I(content) can be set. - content: - description: - - Datastructure of the External Router to create. The format is defined by the - I(content_format) parameter. It's the same datastructure that is returned - on success in I(value). - state: - description: - - Indicate what is the expected state of the External Router (present or not). - default: present - choices: ['present', 'absent'] - loopback: - description: - - IP address of the Loopback interface of the external_router. - asn: - description: - - ASN id of the external_router. -''' - -EXAMPLES = ''' - -- name: "Create an External Router" - aos_external_router: - session: "{{ aos_session }}" - name: "my-external-router" - loopback: 10.0.0.1 - asn: 65000 - state: present - -- name: "Check if an External Router exist by ID" - aos_external_router: - session: "{{ aos_session }}" - name: "45ab26fc-c2ed-4307-b330-0870488fa13e" - state: present - -- name: "Delete an External Router by name" - aos_external_router: - session: "{{ aos_session }}" - name: "my-external-router" - state: absent - -- name: "Delete an External Router by id" - aos_external_router: - session: "{{ aos_session }}" - id: "45ab26fc-c2ed-4307-b330-0870488fa13e" - state: absent - -# Save an External Router to a file -- name: "Access External Router 1/3" - aos_external_router: - session: "{{ aos_session }}" - name: "my-external-router" - state: present - register: external_router - -- name: "Save External Router into a file in JSON 2/3" - copy: - content: "{{ external_router.value | to_nice_json }}" - dest: external_router_saved.json - -- name: "Save External Router into a file in YAML 3/3" - copy: - content: "{{ external_router.value | to_nice_yaml }}" - dest: external_router_saved.yaml - -- name: "Load External Router from a JSON file" - aos_external_router: - session: "{{ aos_session }}" - content: "{{ lookup('file', 'resources/external_router_saved.json') }}" - state: present - -- name: "Load External Router from a YAML file" - aos_external_router: - session: "{{ aos_session }}" - content: "{{ lookup('file', 'resources/external_router_saved.yaml') }}" - state: present -''' - -RETURNS = ''' -name: - description: Name of the External Router - returned: always - type: str - sample: Server-IpAddrs - -id: - description: AOS unique ID assigned to the External Router - returned: always - type: str - sample: fcc4ac1c-e249-4fe7-b458-2138bfb44c06 - -value: - description: Value of the object as returned by the AOS Server - returned: always - type: dict - sample: {'...'} -''' - -import json -import time - -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.network.aos.aos import get_aos_session, find_collection_item, do_load_resource, check_aos_version, content_to_dict - - -def create_new_ext_router(module, my_ext_router, name, loopback, asn): - - # Create value - datum = dict(display_name=name, address=loopback, asn=asn) - - my_ext_router.datum = datum - - # Write to AOS - return my_ext_router.write() - -######################################################### -# State Processing -######################################################### - - -def ext_router_absent(module, aos, my_ext_router): - - margs = module.params - - # If the module do not exist, return directly - if my_ext_router.exists is False: - module.exit_json(changed=False, - name=margs['name'], - id=margs['id'], - value={}) - - # If not in check mode, delete External Router - if not module.check_mode: - try: - # Add Sleep before delete to workaround a bug in AOS - time.sleep(2) - my_ext_router.delete() - except Exception: - module.fail_json(msg="An error occurred, while trying to delete the External Router") - - module.exit_json(changed=True, - name=my_ext_router.name, - id=my_ext_router.id, - value={}) - - -def ext_router_present(module, aos, my_ext_router): - - margs = module.params - - # if content is defined, create object from Content - if my_ext_router.exists is False and margs['content'] is not None: - do_load_resource(module, aos.ExternalRouters, module.params['content']['display_name']) - - # if my_ext_router doesn't exist already, create a new one - if my_ext_router.exists is False and margs['name'] is None: - module.fail_json(msg="Name is mandatory for module that don't exist currently") - - elif my_ext_router.exists is False: - - if not module.check_mode: - try: - my_new_ext_router = create_new_ext_router(module, - my_ext_router, - margs['name'], - margs['loopback'], - margs['asn']) - my_ext_router = my_new_ext_router - except Exception: - module.fail_json(msg="An error occurred while trying to create a new External Router") - - module.exit_json(changed=True, - name=my_ext_router.name, - id=my_ext_router.id, - value=my_ext_router.value) - - # if external Router already exist, check if loopback and ASN are the same - # if same just return the object and report change false - loopback = None - asn = None - - # Identify the Loopback, parameter 'loopback' has priority over 'content' - if margs['loopback'] is not None: - loopback = margs['loopback'] - elif margs['content'] is not None: - if 'address' in margs['content'].keys(): - loopback = margs['content']['address'] - - # Identify the ASN, parameter 'asn' has priority over 'content' - if margs['asn'] is not None: - asn = margs['asn'] - elif margs['content'] is not None: - if 'asn' in margs['content'].keys(): - asn = margs['content']['asn'] - - # Compare Loopback and ASN if defined - if loopback is not None: - if loopback != my_ext_router.value['address']: - module.fail_json(msg="my_ext_router already exist but Loopback is different, currently not supported to update a module") - - if asn is not None: - if int(asn) != int(my_ext_router.value['asn']): - module.fail_json(msg="my_ext_router already exist but ASN is different, currently not supported to update a module") - - module.exit_json(changed=False, - name=my_ext_router.name, - id=my_ext_router.id, - value=my_ext_router.value) - -######################################################### -# Main Function -######################################################### - - -def ext_router(module): - - margs = module.params - - try: - aos = get_aos_session(module, margs['session']) - except Exception: - module.fail_json(msg="Unable to login to the AOS server") - - item_name = False - item_id = False - - if margs['content'] is not None: - - content = content_to_dict(module, margs['content']) - - if 'display_name' in content.keys(): - item_name = content['display_name'] - else: - module.fail_json(msg="Unable to extract 'display_name' from 'content'") - - elif margs['name'] is not None: - item_name = margs['name'] - - elif margs['id'] is not None: - item_id = margs['id'] - - # ---------------------------------------------------- - # Find Object if available based on ID or Name - # ---------------------------------------------------- - try: - my_ext_router = find_collection_item(aos.ExternalRouters, - item_name=item_name, - item_id=item_id) - except Exception: - module.fail_json(msg="Unable to find the IP Pool based on name or ID, something went wrong") - - # ---------------------------------------------------- - # Proceed based on State value - # ---------------------------------------------------- - if margs['state'] == 'absent': - - ext_router_absent(module, aos, my_ext_router) - - elif margs['state'] == 'present': - - ext_router_present(module, aos, my_ext_router) - - -def main(): - module = AnsibleModule( - argument_spec=dict( - session=dict(required=True, type="dict"), - name=dict(required=False), - id=dict(required=False), - content=dict(required=False, type="json"), - state=dict(required=False, - choices=['present', 'absent'], - default="present"), - loopback=dict(required=False), - asn=dict(required=False) - ), - mutually_exclusive=[('name', 'id', 'content')], - required_one_of=[('name', 'id', 'content')], - supports_check_mode=True - ) - - # Check if aos-pyez is present and match the minimum version - check_aos_version(module, '0.6.0') - - ext_router(module) +from ansible.module_utils.common.removed import removed_module -if __name__ == "__main__": - main() +if __name__ == '__main__': + removed_module(removed_in='2.9') diff --git a/lib/ansible/modules/network/aos/_aos_ip_pool.py b/lib/ansible/modules/network/aos/_aos_ip_pool.py index d2f504f63d5..9b960f60121 100644 --- a/lib/ansible/modules/network/aos/_aos_ip_pool.py +++ b/lib/ansible/modules/network/aos/_aos_ip_pool.py @@ -1,353 +1,15 @@ #!/usr/bin/python # # (c) 2017 Apstra 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 . -# +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['deprecated'], + 'status': ['removed'], 'supported_by': 'community'} -DOCUMENTATION = ''' ---- -module: aos_ip_pool -author: Damien Garros (@dgarros) -version_added: "2.3" -short_description: Manage AOS IP Pool -deprecated: - removed_in: "2.9" - why: This module does not support AOS 2.1 or later - alternative: See new modules at U(https://www.ansible.com/ansible-apstra). -description: - - Apstra AOS Ip Pool module let you manage your IP Pool easily. You can create - create and delete IP Pool by Name, ID or by using a JSON File. This module - is idempotent and support the I(check) mode. It's using the AOS REST API. -requirements: - - "aos-pyez >= 0.6.0" -options: - session: - description: - - An existing AOS session as obtained by M(aos_login) module. - required: true - name: - description: - - Name of the IP Pool to manage. - Only one of I(name), I(id) or I(content) can be set. - id: - description: - - AOS Id of the IP Pool to manage (can't be used to create a new IP Pool), - Only one of I(name), I(id) or I(content) can be set. - content: - description: - - Datastructure of the IP Pool to manage. The data can be in YAML / JSON or - directly a variable. It's the same datastructure that is returned - on success in I(value). - state: - description: - - Indicate what is the expected state of the IP Pool (present or not). - default: present - choices: ['present', 'absent'] - subnets: - description: - - List of subnet that needs to be part of the IP Pool. -''' - -EXAMPLES = ''' - -- name: "Create an IP Pool with one subnet" - aos_ip_pool: - session: "{{ aos_session }}" - name: "my-ip-pool" - subnets: [ 172.10.0.0/16 ] - state: present - -- name: "Create an IP Pool with multiple subnets" - aos_ip_pool: - session: "{{ aos_session }}" - name: "my-other-ip-pool" - subnets: [ 172.10.0.0/16, 192.168.0.0./24 ] - state: present - -- name: "Check if an IP Pool exist with same subnets by ID" - aos_ip_pool: - session: "{{ aos_session }}" - name: "45ab26fc-c2ed-4307-b330-0870488fa13e" - subnets: [ 172.10.0.0/16, 192.168.0.0./24 ] - state: present - -- name: "Delete an IP Pool by name" - aos_ip_pool: - session: "{{ aos_session }}" - name: "my-ip-pool" - state: absent - -- name: "Delete an IP pool by id" - aos_ip_pool: - session: "{{ aos_session }}" - id: "45ab26fc-c2ed-4307-b330-0870488fa13e" - state: absent - -# Save an IP Pool to a file - -- name: "Access IP Pool 1/3" - aos_ip_pool: - session: "{{ aos_session }}" - name: "my-ip-pool" - subnets: [ 172.10.0.0/16, 172.12.0.0/16 ] - state: present - register: ip_pool - -- name: "Save Ip Pool into a file in JSON 2/3" - copy: - content: "{{ ip_pool.value | to_nice_json }}" - dest: ip_pool_saved.json - -- name: "Save Ip Pool into a file in YAML 3/3" - copy: - content: "{{ ip_pool.value | to_nice_yaml }}" - dest: ip_pool_saved.yaml - -- name: "Load IP Pool from a JSON file" - aos_ip_pool: - session: "{{ aos_session }}" - content: "{{ lookup('file', 'resources/ip_pool_saved.json') }}" - state: present - -- name: "Load IP Pool from a YAML file" - aos_ip_pool: - session: "{{ aos_session }}" - content: "{{ lookup('file', 'resources/ip_pool_saved.yaml') }}" - state: present - -- name: "Load IP Pool from a Variable" - aos_ip_pool: - session: "{{ aos_session }}" - content: - display_name: my-ip-pool - id: 4276738d-6f86-4034-9656-4bff94a34ea7 - subnets: - - network: 172.10.0.0/16 - - network: 172.12.0.0/16 - state: present -''' - -RETURNS = ''' -name: - description: Name of the IP Pool - returned: always - type: str - sample: Server-IpAddrs - -id: - description: AOS unique ID assigned to the IP Pool - returned: always - type: str - sample: fcc4ac1c-e249-4fe7-b458-2138bfb44c06 - -value: - description: Value of the object as returned by the AOS Server - returned: always - type: dict - sample: {'...'} -''' - -import json - -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.network.aos.aos import get_aos_session, find_collection_item, do_load_resource, check_aos_version, content_to_dict - - -def get_list_of_subnets(ip_pool): - subnets = [] - - for subnet in ip_pool.value['subnets']: - subnets.append(subnet['network']) - - return subnets - - -def create_new_ip_pool(ip_pool, name, subnets): - - # Create value - datum = dict(display_name=name, subnets=[]) - for subnet in subnets: - datum['subnets'].append(dict(network=subnet)) - - ip_pool.datum = datum - - # Write to AOS - return ip_pool.write() - -######################################################### -# State Processing -######################################################### - - -def ip_pool_absent(module, aos, my_pool): - - margs = module.params - - # If the module do not exist, return directly - if my_pool.exists is False: - module.exit_json(changed=False, name=margs['name'], id='', value={}) - - # Check if object is currently in Use or Not - # If in Use, return an error - if my_pool.value: - if my_pool.value['status'] != 'not_in_use': - module.fail_json(msg="unable to delete this ip Pool, currently in use") - else: - module.fail_json(msg="Ip Pool object has an invalid format, value['status'] must be defined") - - # If not in check mode, delete Ip Pool - if not module.check_mode: - try: - my_pool.delete() - except Exception: - module.fail_json(msg="An error occurred, while trying to delete the IP Pool") - - module.exit_json(changed=True, - name=my_pool.name, - id=my_pool.id, - value={}) - - -def ip_pool_present(module, aos, my_pool): - - margs = module.params - - # if content is defined, create object from Content - try: - if margs['content'] is not None: - - if 'display_name' in module.params['content'].keys(): - do_load_resource(module, aos.IpPools, module.params['content']['display_name']) - else: - module.fail_json(msg="Unable to find display_name in 'content', Mandatory") - - except Exception: - module.fail_json(msg="Unable to load resource from content, something went wrong") - - # if ip_pool doesn't exist already, create a new one - - if my_pool.exists is False and 'name' not in margs.keys(): - module.fail_json(msg="Name is mandatory for module that don't exist currently") - - elif my_pool.exists is False: - - if not module.check_mode: - try: - my_new_pool = create_new_ip_pool(my_pool, margs['name'], margs['subnets']) - my_pool = my_new_pool - except Exception: - module.fail_json(msg="An error occurred while trying to create a new IP Pool ") - - module.exit_json(changed=True, - name=my_pool.name, - id=my_pool.id, - value=my_pool.value) - - # if pool already exist, check if list of network is the same - # if same just return the object and report change false - if set(get_list_of_subnets(my_pool)) == set(margs['subnets']): - module.exit_json(changed=False, - name=my_pool.name, - id=my_pool.id, - value=my_pool.value) - else: - module.fail_json(msg="ip_pool already exist but value is different, currently not supported to update a module") - -######################################################### -# Main Function -######################################################### - - -def ip_pool(module): - - margs = module.params - - try: - aos = get_aos_session(module, margs['session']) - except Exception: - module.fail_json(msg="Unable to login to the AOS server") - - item_name = False - item_id = False - - if margs['content'] is not None: - - content = content_to_dict(module, margs['content']) - - if 'display_name' in content.keys(): - item_name = content['display_name'] - else: - module.fail_json(msg="Unable to extract 'display_name' from 'content'") - - elif margs['name'] is not None: - item_name = margs['name'] - - elif margs['id'] is not None: - item_id = margs['id'] - - # ---------------------------------------------------- - # Find Object if available based on ID or Name - # ---------------------------------------------------- - try: - my_pool = find_collection_item(aos.IpPools, - item_name=item_name, - item_id=item_id) - except Exception: - module.fail_json(msg="Unable to find the IP Pool based on name or ID, something went wrong") - - # ---------------------------------------------------- - # Proceed based on State value - # ---------------------------------------------------- - if margs['state'] == 'absent': - - ip_pool_absent(module, aos, my_pool) - - elif margs['state'] == 'present': - - ip_pool_present(module, aos, my_pool) - - -def main(): - module = AnsibleModule( - argument_spec=dict( - session=dict(required=True, type="dict"), - name=dict(required=False), - id=dict(required=False), - content=dict(required=False, type="json"), - state=dict(required=False, - choices=['present', 'absent'], - default="present"), - subnets=dict(required=False, type="list") - ), - mutually_exclusive=[('name', 'id', 'content')], - required_one_of=[('name', 'id', 'content')], - supports_check_mode=True - ) - - # Check if aos-pyez is present and match the minimum version - check_aos_version(module, '0.6.0') - - ip_pool(module) +from ansible.module_utils.common.removed import removed_module -if __name__ == "__main__": - main() +if __name__ == '__main__': + removed_module(removed_in='2.9') diff --git a/lib/ansible/modules/network/aos/_aos_logical_device.py b/lib/ansible/modules/network/aos/_aos_logical_device.py index fb16cc93823..9b960f60121 100644 --- a/lib/ansible/modules/network/aos/_aos_logical_device.py +++ b/lib/ansible/modules/network/aos/_aos_logical_device.py @@ -1,264 +1,15 @@ #!/usr/bin/python # # (c) 2017 Apstra 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 . -# +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['deprecated'], + 'status': ['removed'], 'supported_by': 'community'} -DOCUMENTATION = ''' ---- -module: aos_logical_device -author: Damien Garros (@dgarros) -version_added: "2.3" -short_description: Manage AOS Logical Device -deprecated: - removed_in: "2.9" - why: This module does not support AOS 2.1 or later - alternative: See new modules at U(https://www.ansible.com/ansible-apstra). -description: - - Apstra AOS Logical Device module let you manage your Logical Devices easily. - You can create create and delete Logical Device by Name, ID or by using a JSON File. - This module is idempotent and support the I(check) mode. - It's using the AOS REST API. -requirements: - - "aos-pyez >= 0.6.0" -options: - session: - description: - - An existing AOS session as obtained by M(aos_login) module. - required: true - name: - description: - - Name of the Logical Device to manage. - Only one of I(name), I(id) or I(content) can be set. - id: - description: - - AOS Id of the Logical Device to manage (can't be used to create a new Logical Device), - Only one of I(name), I(id) or I(content) can be set. - content: - description: - - Datastructure of the Logical Device to create. The data can be in YAML / JSON or - directly a variable. It's the same datastructure that is returned - on success in I(value). - state: - description: - - Indicate what is the expected state of the Logical Device (present or not). - default: present - choices: ['present', 'absent'] -''' - -EXAMPLES = ''' - -- name: "Delete a Logical Device by name" - aos_logical_device: - session: "{{ aos_session }}" - name: "my-logical-device" - state: absent - -- name: "Delete a Logical Device by id" - aos_logical_device: - session: "{{ aos_session }}" - id: "45ab26fc-c2ed-4307-b330-0870488fa13e" - state: absent - -# Save a Logical Device to a file - -- name: "Access Logical Device 1/3" - aos_logical_device: - session: "{{ aos_session }}" - name: "my-logical-device" - state: present - register: logical_device - -- name: "Save Logical Device into a JSON file 2/3" - copy: - content: "{{ logical_device.value | to_nice_json }}" - dest: logical_device_saved.json -- name: "Save Logical Device into a YAML file 3/3" - copy: - content: "{{ logical_device.value | to_nice_yaml }}" - dest: logical_device_saved.yaml - -- name: "Load Logical Device from a JSON file" - aos_logical_device: - session: "{{ aos_session }}" - content: "{{ lookup('file', 'resources/logical_device_saved.json') }}" - state: present - -- name: "Load Logical Device from a YAML file" - aos_logical_device: - session: "{{ aos_session }}" - content: "{{ lookup('file', 'resources/logical_device_saved.yaml') }}" - state: present -''' - -RETURNS = ''' -name: - description: Name of the Logical Device - returned: always - type: str - sample: AOS-1x25-1 - -id: - description: AOS unique ID assigned to the Logical Device - returned: always - type: str - sample: fcc4ac1c-e249-4fe7-b458-2138bfb44c06 - -value: - description: Value of the object as returned by the AOS Server - returned: always - type: dict - sample: {'...'} -''' - -import json -import time - -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.network.aos.aos import get_aos_session, find_collection_item, do_load_resource, check_aos_version, content_to_dict - -######################################################### -# State Processing -######################################################### - - -def logical_device_absent(module, aos, my_logical_dev): - - margs = module.params - - # If the module do not exist, return directly - if my_logical_dev.exists is False: - module.exit_json(changed=False, - name=margs['name'], - id=margs['id'], - value={}) - - # If not in check mode, delete Logical Device - if not module.check_mode: - try: - # Need to way 1sec before a delete to workaround a current limitation in AOS - time.sleep(1) - my_logical_dev.delete() - except Exception: - module.fail_json(msg="An error occurred, while trying to delete the Logical Device") - - module.exit_json(changed=True, - name=my_logical_dev.name, - id=my_logical_dev.id, - value={}) - - -def logical_device_present(module, aos, my_logical_dev): - - margs = module.params - - if margs['content'] is not None: - - if 'display_name' in module.params['content'].keys(): - do_load_resource(module, aos.LogicalDevices, module.params['content']['display_name']) - else: - module.fail_json(msg="Unable to find display_name in 'content', Mandatory") - - # if logical_device doesn't exist already, create a new one - if my_logical_dev.exists is False and 'content' not in margs.keys(): - module.fail_json(msg="'content' is mandatory for module that don't exist currently") - - module.exit_json(changed=False, - name=my_logical_dev.name, - id=my_logical_dev.id, - value=my_logical_dev.value) - -######################################################### -# Main Function -######################################################### - - -def logical_device(module): - - margs = module.params - - try: - aos = get_aos_session(module, margs['session']) - except Exception: - module.fail_json(msg="Unable to login to the AOS server") - - item_name = False - item_id = False - - if margs['content'] is not None: - - content = content_to_dict(module, margs['content']) - - if 'display_name' in content.keys(): - item_name = content['display_name'] - else: - module.fail_json(msg="Unable to extract 'display_name' from 'content'") - - elif margs['name'] is not None: - item_name = margs['name'] - - elif margs['id'] is not None: - item_id = margs['id'] - - # ---------------------------------------------------- - # Find Object if available based on ID or Name - # ---------------------------------------------------- - my_logical_dev = find_collection_item(aos.LogicalDevices, - item_name=item_name, - item_id=item_id) - - # ---------------------------------------------------- - # Proceed based on State value - # ---------------------------------------------------- - if margs['state'] == 'absent': - - logical_device_absent(module, aos, my_logical_dev) - - elif margs['state'] == 'present': - - logical_device_present(module, aos, my_logical_dev) - - -def main(): - module = AnsibleModule( - argument_spec=dict( - session=dict(required=True, type="dict"), - name=dict(required=False), - id=dict(required=False), - content=dict(required=False, type="json"), - state=dict(required=False, - choices=['present', 'absent'], - default="present") - ), - mutually_exclusive=[('name', 'id', 'content')], - required_one_of=[('name', 'id', 'content')], - supports_check_mode=True - ) - - # Check if aos-pyez is present and match the minimum version - check_aos_version(module, '0.6.0') - - logical_device(module) +from ansible.module_utils.common.removed import removed_module -if __name__ == "__main__": - main() +if __name__ == '__main__': + removed_module(removed_in='2.9') diff --git a/lib/ansible/modules/network/aos/_aos_logical_device_map.py b/lib/ansible/modules/network/aos/_aos_logical_device_map.py index bd0b0a2ae84..9b960f60121 100644 --- a/lib/ansible/modules/network/aos/_aos_logical_device_map.py +++ b/lib/ansible/modules/network/aos/_aos_logical_device_map.py @@ -1,286 +1,15 @@ #!/usr/bin/python # # (c) 2017 Apstra 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 . -# +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['deprecated'], + 'status': ['removed'], 'supported_by': 'community'} -DOCUMENTATION = ''' ---- -module: aos_logical_device_map -author: Damien Garros (@dgarros) -version_added: "2.3" -short_description: Manage AOS Logical Device Map -deprecated: - removed_in: "2.9" - why: This module does not support AOS 2.1 or later - alternative: See new modules at U(https://www.ansible.com/ansible-apstra). -description: - - Apstra AOS Logical Device Map module let you manage your Logical Device Map easily. You can create - create and delete Logical Device Map by Name, ID or by using a JSON File. This module - is idempotent and support the I(check) mode. It's using the AOS REST API. -requirements: - - "aos-pyez >= 0.6.0" -options: - session: - description: - - An existing AOS session as obtained by M(aos_login) module. - required: true - name: - description: - - Name of the Logical Device Map to manage. - Only one of I(name), I(id) or I(content) can be set. - id: - description: - - AOS Id of the Logical Device Map to manage (can't be used to create a new Logical Device Map), - Only one of I(name), I(id) or I(content) can be set. - content: - description: - - Datastructure of the Logical Device Map to manage. The data can be in YAML / JSON or - directly a variable. It's the same datastructure that is returned - on success in I(value). Only one of I(name), I(id) or I(content) can be set. - state: - description: - - Indicate what is the expected state of the Logical Device Map (present or not). - default: present - choices: ['present', 'absent'] -''' - -EXAMPLES = ''' - -- name: "Create an Logical Device Map with one subnet" - aos_logical_device_map: - session: "{{ aos_session }}" - name: "my-logical-device-map" - state: present - -- name: "Create an Logical Device Map with multiple subnets" - aos_logical_device_map: - session: "{{ aos_session }}" - name: "my-other-logical-device-map" - state: present - -- name: "Check if an Logical Device Map exist with same subnets by ID" - aos_logical_device_map: - session: "{{ aos_session }}" - name: "45ab26fc-c2ed-4307-b330-0870488fa13e" - state: present - -- name: "Delete an Logical Device Map by name" - aos_logical_device_map: - session: "{{ aos_session }}" - name: "my-logical-device-map" - state: absent - -- name: "Delete an Logical Device Map by id" - aos_logical_device_map: - session: "{{ aos_session }}" - id: "45ab26fc-c2ed-4307-b330-0870488fa13e" - state: absent - -# Save an Logical Device Map to a file - -- name: "Access Logical Device Map 1/3" - aos_logical_device_map: - session: "{{ aos_session }}" - name: "my-logical-device-map" - state: present - register: logical_device_map - -- name: "Save Logical Device Map into a file in JSON 2/3" - copy: - content: "{{ logical_device_map.value | to_nice_json }}" - dest: logical_device_map_saved.json - -- name: "Save Logical Device Map into a file in YAML 3/3" - copy: - content: "{{ logical_device_map.value | to_nice_yaml }}" - dest: logical_device_map_saved.yaml - -- name: "Load Logical Device Map from a JSON file" - aos_logical_device_map: - session: "{{ aos_session }}" - content: "{{ lookup('file', 'resources/logical_device_map_saved.json') }}" - state: present - -- name: "Load Logical Device Map from a YAML file" - aos_logical_device_map: - session: "{{ aos_session }}" - content: "{{ lookup('file', 'resources/logical_device_map_saved.yaml') }}" - state: present - -''' - -RETURNS = ''' -name: - description: Name of the Logical Device Map - returned: always - type: str - sample: Server-IpAddrs - -id: - description: AOS unique ID assigned to the Logical Device Map - returned: always - type: str - sample: fcc4ac1c-e249-4fe7-b458-2138bfb44c06 - -value: - description: Value of the object as returned by the AOS Server - returned: always - type: dict - sample: {'...'} -''' - -import json -import time - -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.network.aos.aos import get_aos_session, find_collection_item, do_load_resource, check_aos_version, content_to_dict - -######################################################### -# State Processing -######################################################### - - -def logical_device_map_absent(module, aos, my_log_dev_map): - - margs = module.params - - # If the module do not exist, return directly - if my_log_dev_map.exists is False: - module.exit_json(changed=False, name=margs['name'], id='', value={}) - - # If not in check mode, delete Logical Device Map - if not module.check_mode: - try: - # Need to wait for 1sec before a delete to workaround a current - # limitation in AOS - time.sleep(1) - my_log_dev_map.delete() - except Exception: - module.fail_json(msg="An error occurred, while trying to delete the Logical Device Map") - - module.exit_json(changed=True, - name=my_log_dev_map.name, - id=my_log_dev_map.id, - value={}) - - -def logical_device_map_present(module, aos, my_log_dev_map): - - margs = module.params - - # if content is defined, create object from Content - if margs['content'] is not None: - - if 'display_name' in module.params['content'].keys(): - do_load_resource(module, aos.LogicalDeviceMaps, module.params['content']['display_name']) - else: - module.fail_json(msg="Unable to find display_name in 'content', Mandatory") - - # if my_log_dev_map doesn't exist already, create a new one - - if my_log_dev_map.exists is False and 'content' not in margs.keys(): - module.fail_json(msg="'Content' is mandatory for module that don't exist currently") - - module.exit_json(changed=False, - name=my_log_dev_map.name, - id=my_log_dev_map.id, - value=my_log_dev_map.value) - -######################################################### -# Main Function -######################################################### - - -def logical_device_map(module): - - margs = module.params - - try: - aos = get_aos_session(module, margs['session']) - except Exception: - module.fail_json(msg="Unable to login to the AOS server") - - item_name = False - item_id = False - - if margs['content'] is not None: - - content = content_to_dict(module, margs['content']) - - if 'display_name' in content.keys(): - item_name = content['display_name'] - else: - module.fail_json(msg="Unable to extract 'display_name' from 'content'") - - elif margs['name'] is not None: - item_name = margs['name'] - - elif margs['id'] is not None: - item_id = margs['id'] - - # ---------------------------------------------------- - # Find Object if available based on ID or Name - # ---------------------------------------------------- - try: - my_log_dev_map = find_collection_item(aos.LogicalDeviceMaps, - item_name=item_name, - item_id=item_id) - except Exception: - module.fail_json(msg="Unable to find the Logical Device Map based on name or ID, something went wrong") - - # ---------------------------------------------------- - # Proceed based on State value - # ---------------------------------------------------- - if margs['state'] == 'absent': - - logical_device_map_absent(module, aos, my_log_dev_map) - - elif margs['state'] == 'present': - - logical_device_map_present(module, aos, my_log_dev_map) - - -def main(): - module = AnsibleModule( - argument_spec=dict( - session=dict(required=True, type="dict"), - name=dict(required=False), - id=dict(required=False), - content=dict(required=False, type="json"), - state=dict(required=False, - choices=['present', 'absent'], - default="present") - ), - mutually_exclusive=[('name', 'id', 'content')], - required_one_of=[('name', 'id', 'content')], - supports_check_mode=True - ) - - # Check if aos-pyez is present and match the minimum version - check_aos_version(module, '0.6.0') - - logical_device_map(module) +from ansible.module_utils.common.removed import removed_module -if __name__ == "__main__": - main() +if __name__ == '__main__': + removed_module(removed_in='2.9') diff --git a/lib/ansible/modules/network/aos/_aos_login.py b/lib/ansible/modules/network/aos/_aos_login.py index 7d53338b4b3..9b960f60121 100644 --- a/lib/ansible/modules/network/aos/_aos_login.py +++ b/lib/ansible/modules/network/aos/_aos_login.py @@ -1,139 +1,15 @@ #!/usr/bin/python # # (c) 2017 Apstra 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 . -# +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['deprecated'], + 'status': ['removed'], 'supported_by': 'community'} -DOCUMENTATION = ''' ---- -module: aos_login -author: jeremy@apstra.com (@jeremyschulman) -version_added: "2.3" -short_description: Login to AOS server for session token -deprecated: - removed_in: "2.9" - why: This module does not support AOS 2.1 or later - alternative: See new modules at U(https://www.ansible.com/ansible-apstra). -description: - - Obtain the AOS server session token by providing the required - username and password credentials. Upon successful authentication, - this module will return the session-token that is required by all - subsequent AOS module usage. On success the module will automatically populate - ansible facts with the variable I(aos_session) - This module is not idempotent and do not support check mode. -requirements: - - "aos-pyez >= 0.6.1" -options: - server: - description: - - Address of the AOS Server on which you want to open a connection. - required: true - port: - description: - - Port number to use when connecting to the AOS server. - default: 443 - user: - description: - - Login username to use when connecting to the AOS server. - default: admin - passwd: - description: - - Password to use when connecting to the AOS server. - default: admin -''' - -EXAMPLES = ''' - -- name: Create a session with the AOS-server - aos_login: - server: "{{ inventory_hostname }}" - user: admin - passwd: admin - -- name: Use the newly created session (register is not mandatory) - aos_ip_pool: - session: "{{ aos_session }}" - name: my_ip_pool - state: present -''' - -RETURNS = ''' -aos_session: - description: Authenticated session information - returned: always - type: dict - sample: { 'url': , 'headers': {...} } -''' - -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.network.aos.aos import check_aos_version - -try: - from apstra.aosom.session import Session - import apstra.aosom.exc as aosExc - - HAS_AOS_PYEZ = True -except ImportError: - HAS_AOS_PYEZ = False - - -def aos_login(module): - - mod_args = module.params - - aos = Session(server=mod_args['server'], port=mod_args['port'], - user=mod_args['user'], passwd=mod_args['passwd']) - - try: - aos.login() - except aosExc.LoginServerUnreachableError: - module.fail_json( - msg="AOS-server [%s] API not available/reachable, check server" % aos.server) - - except aosExc.LoginAuthError: - module.fail_json(msg="AOS-server login credentials failed") - - module.exit_json(changed=False, - ansible_facts=dict(aos_session=aos.session), - aos_session=dict(aos_session=aos.session)) - - -def main(): - module = AnsibleModule( - argument_spec=dict( - server=dict(required=True), - port=dict(default='443', type="int"), - user=dict(default='admin'), - passwd=dict(default='admin', no_log=True))) - - if not HAS_AOS_PYEZ: - module.fail_json(msg='aos-pyez is not installed. Please see details ' - 'here: https://github.com/Apstra/aos-pyez') - - # Check if aos-pyez is present and match the minimum version - check_aos_version(module, '0.6.1') - - aos_login(module) +from ansible.module_utils.common.removed import removed_module if __name__ == '__main__': - main() + removed_module(removed_in='2.9') diff --git a/lib/ansible/modules/network/aos/_aos_rack_type.py b/lib/ansible/modules/network/aos/_aos_rack_type.py index c94a422df26..9b960f60121 100644 --- a/lib/ansible/modules/network/aos/_aos_rack_type.py +++ b/lib/ansible/modules/network/aos/_aos_rack_type.py @@ -1,261 +1,15 @@ #!/usr/bin/python # # (c) 2017 Apstra 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 . -# +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['deprecated'], + 'status': ['removed'], 'supported_by': 'community'} -DOCUMENTATION = ''' ---- -module: aos_rack_type -author: Damien Garros (@dgarros) -version_added: "2.3" -short_description: Manage AOS Rack Type -deprecated: - removed_in: "2.9" - why: This module does not support AOS 2.1 or later - alternative: See new modules at U(https://www.ansible.com/ansible-apstra). -description: - - Apstra AOS Rack Type module let you manage your Rack Type easily. - You can create create and delete Rack Type by Name, ID or by using a JSON File. - This module is idempotent and support the I(check) mode. - It's using the AOS REST API. -requirements: - - "aos-pyez >= 0.6.0" -options: - session: - description: - - An existing AOS session as obtained by M(aos_login) module. - required: true - name: - description: - - Name of the Rack Type to manage. - Only one of I(name), I(id) or I(content) can be set. - id: - description: - - AOS Id of the Rack Type to manage (can't be used to create a new Rack Type), - Only one of I(name), I(id) or I(content) can be set. - content: - description: - - Datastructure of the Rack Type to create. The data can be in YAML / JSON or - directly a variable. It's the same datastructure that is returned - on success in I(value). - state: - description: - - Indicate what is the expected state of the Rack Type (present or not). - default: present - choices: ['present', 'absent'] -''' - -EXAMPLES = ''' - -- name: "Delete a Rack Type by name" - aos_rack_type: - session: "{{ aos_session }}" - name: "my-rack-type" - state: absent - -- name: "Delete a Rack Type by id" - aos_rack_type: - session: "{{ aos_session }}" - id: "45ab26fc-c2ed-4307-b330-0870488fa13e" - state: absent - -# Save a Rack Type to a file - -- name: "Access Rack Type 1/3" - aos_rack_type: - session: "{{ aos_session }}" - name: "my-rack-type" - state: present - register: rack_type - -- name: "Save Rack Type into a JSON file 2/3" - copy: - content: "{{ rack_type.value | to_nice_json }}" - dest: rack_type_saved.json -- name: "Save Rack Type into a YAML file 3/3" - copy: - content: "{{ rack_type.value | to_nice_yaml }}" - dest: rack_type_saved.yaml - -- name: "Load Rack Type from a JSON file" - aos_rack_type: - session: "{{ aos_session }}" - content: "{{ lookup('file', 'resources/rack_type_saved.json') }}" - state: present - -- name: "Load Rack Type from a YAML file" - aos_rack_type: - session: "{{ aos_session }}" - content: "{{ lookup('file', 'resources/rack_type_saved.yaml') }}" - state: present -''' - -RETURNS = ''' -name: - description: Name of the Rack Type - returned: always - type: str - sample: AOS-1x25-1 - -id: - description: AOS unique ID assigned to the Rack Type - returned: always - type: str - sample: fcc4ac1c-e249-4fe7-b458-2138bfb44c06 - -value: - description: Value of the object as returned by the AOS Server - returned: always - type: dict - sample: {'...'} -''' - -import json - -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.network.aos.aos import get_aos_session, find_collection_item, do_load_resource, check_aos_version, content_to_dict - -######################################################### -# State Processing -######################################################### - - -def rack_type_absent(module, aos, my_rack_type): - - margs = module.params - - # If the module do not exist, return directly - if my_rack_type.exists is False: - module.exit_json(changed=False, - name=margs['name'], - id=margs['id'], - value={}) - - # If not in check mode, delete Rack Type - if not module.check_mode: - try: - my_rack_type.delete() - except Exception: - module.fail_json(msg="An error occurred, while trying to delete the Rack Type") - - module.exit_json(changed=True, - name=my_rack_type.name, - id=my_rack_type.id, - value={}) - - -def rack_type_present(module, aos, my_rack_type): - - margs = module.params - - if margs['content'] is not None: - - if 'display_name' in module.params['content'].keys(): - do_load_resource(module, aos.RackTypes, module.params['content']['display_name']) - else: - module.fail_json(msg="Unable to find display_name in 'content', Mandatory") - - # if rack_type doesn't exist already, create a new one - if my_rack_type.exists is False and 'content' not in margs.keys(): - module.fail_json(msg="'content' is mandatory for module that don't exist currently") - - module.exit_json(changed=False, - name=my_rack_type.name, - id=my_rack_type.id, - value=my_rack_type.value) - -######################################################### -# Main Function -######################################################### - - -def rack_type(module): - - margs = module.params - - try: - aos = get_aos_session(module, margs['session']) - except Exception: - module.fail_json(msg="Unable to login to the AOS server") - - item_name = False - item_id = False - - if margs['content'] is not None: - - content = content_to_dict(module, margs['content']) - - if 'display_name' in content.keys(): - item_name = content['display_name'] - else: - module.fail_json(msg="Unable to extract 'display_name' from 'content'") - - elif margs['name'] is not None: - item_name = margs['name'] - - elif margs['id'] is not None: - item_id = margs['id'] - - # ---------------------------------------------------- - # Find Object if available based on ID or Name - # ---------------------------------------------------- - my_rack_type = find_collection_item(aos.RackTypes, - item_name=item_name, - item_id=item_id) - - # ---------------------------------------------------- - # Proceed based on State value - # ---------------------------------------------------- - if margs['state'] == 'absent': - - rack_type_absent(module, aos, my_rack_type) - - elif margs['state'] == 'present': - - rack_type_present(module, aos, my_rack_type) - - -def main(): - module = AnsibleModule( - argument_spec=dict( - session=dict(required=True, type="dict"), - name=dict(required=False), - id=dict(required=False), - content=dict(required=False, type="json"), - state=dict(required=False, - choices=['present', 'absent'], - default="present") - ), - mutually_exclusive=[('name', 'id', 'content')], - required_one_of=[('name', 'id', 'content')], - supports_check_mode=True - ) - - # Check if aos-pyez is present and match the minimum version - check_aos_version(module, '0.6.0') - - rack_type(module) +from ansible.module_utils.common.removed import removed_module -if __name__ == "__main__": - main() +if __name__ == '__main__': + removed_module(removed_in='2.9') diff --git a/lib/ansible/modules/network/aos/_aos_template.py b/lib/ansible/modules/network/aos/_aos_template.py index dc5fc6ceb16..9b960f60121 100644 --- a/lib/ansible/modules/network/aos/_aos_template.py +++ b/lib/ansible/modules/network/aos/_aos_template.py @@ -1,278 +1,15 @@ #!/usr/bin/python # # (c) 2017 Apstra 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 . -# +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['deprecated'], + 'status': ['removed'], 'supported_by': 'community'} -DOCUMENTATION = ''' ---- -module: aos_template -author: Damien Garros (@dgarros) -version_added: "2.3" -short_description: Manage AOS Template -deprecated: - removed_in: "2.9" - why: This module does not support AOS 2.1 or later - alternative: See new modules at U(https://www.ansible.com/ansible-apstra). -description: - - Apstra AOS Template module let you manage your Template easily. You can create - create and delete Template by Name, ID or by using a JSON File. This module - is idempotent and support the I(check) mode. It's using the AOS REST API. -requirements: - - "aos-pyez >= 0.6.0" -options: - session: - description: - - An existing AOS session as obtained by M(aos_login) module. - required: true - name: - description: - - Name of the Template to manage. - Only one of I(name), I(id) or I(src) can be set. - id: - description: - - AOS Id of the Template to manage (can't be used to create a new Template), - Only one of I(name), I(id) or I(src) can be set. - content: - description: - - Datastructure of the Template to create. The data can be in YAML / JSON or - directly a variable. It's the same datastructure that is returned - on success in I(value). - state: - description: - - Indicate what is the expected state of the Template (present or not). - default: present - choices: ['present', 'absent'] -''' - -EXAMPLES = ''' - -- name: "Check if an Template exist by name" - aos_template: - session: "{{ aos_session }}" - name: "my-template" - state: present - -- name: "Check if an Template exist by ID" - aos_template: - session: "{{ aos_session }}" - id: "45ab26fc-c2ed-4307-b330-0870488fa13e" - state: present - -- name: "Delete an Template by name" - aos_template: - session: "{{ aos_session }}" - name: "my-template" - state: absent - -- name: "Delete an Template by id" - aos_template: - session: "{{ aos_session }}" - id: "45ab26fc-c2ed-4307-b330-0870488fa13e" - state: absent - -- name: "Access Template 1/3" - aos_template: - session: "{{ aos_session }}" - name: "my-template" - state: present - register: template - -- name: "Save Template into a JSON file 2/3" - copy: - content: "{{ template.value | to_nice_json }}" - dest: template_saved.json -- name: "Save Template into a YAML file 2/3" - copy: - content: "{{ template.value | to_nice_yaml }}" - dest: template_saved.yaml - -- name: "Load Template from File (Json)" - aos_template: - session: "{{ aos_session }}" - content: "{{ lookup('file', 'resources/template_saved.json') }}" - state: present - -- name: "Load Template from File (yaml)" - aos_template: - session: "{{ aos_session }}" - content: "{{ lookup('file', 'resources/template_saved.yaml') }}" - state: present -''' - -RETURNS = ''' -name: - description: Name of the Template - returned: always - type: str - sample: My-Template - -id: - description: AOS unique ID assigned to the Template - returned: always - type: str - sample: fcc4ac1c-e249-4fe7-b458-2138bfb44c06 - -value: - description: Value of the object as returned by the AOS Server - returned: always - type: dict - sample: {'...'} -''' - -import time -import json - -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.network.aos.aos import get_aos_session, find_collection_item, do_load_resource, check_aos_version, content_to_dict - -######################################################### -# State Processing -######################################################### - - -def template_absent(module, aos, my_template): - - margs = module.params - - # If the module do not exist, return directly - if my_template.exists is False: - module.exit_json(changed=False, - name=margs['name'], - id=margs['id'], - value={}) - - # If not in check mode, delete Template - if not module.check_mode: - try: - # need to way 1sec before delete to workaround a current limitation in AOS - time.sleep(1) - my_template.delete() - except Exception: - module.fail_json(msg="An error occurred, while trying to delete the Template") - - module.exit_json(changed=True, - name=my_template.name, - id=my_template.id, - value={}) - - -def template_present(module, aos, my_template): - - margs = module.params - - # if content is defined, create object from Content - - if margs['content'] is not None: - - if 'display_name' in module.params['content'].keys(): - do_load_resource(module, aos.DesignTemplates, module.params['content']['display_name']) - else: - module.fail_json(msg="Unable to find display_name in 'content', Mandatory") - - # if template doesn't exist already, create a new one - if my_template.exists is False and 'content' not in margs.keys(): - module.fail_json(msg="'content' is mandatory for module that don't exist currently") - - # if module already exist, just return it - module.exit_json(changed=False, - name=my_template.name, - id=my_template.id, - value=my_template.value) - - -######################################################### -# Main Function -######################################################### -def aos_template(module): - - margs = module.params - - try: - aos = get_aos_session(module, margs['session']) - except Exception: - module.fail_json(msg="Unable to login to the AOS server") - - item_name = False - item_id = False - - if margs['content'] is not None: - - content = content_to_dict(module, margs['content']) - - if 'display_name' in content.keys(): - item_name = content['display_name'] - else: - module.fail_json(msg="Unable to extract 'display_name' from 'content'") - - elif margs['name'] is not None: - item_name = margs['name'] - - elif margs['id'] is not None: - item_id = margs['id'] - - # ---------------------------------------------------- - # Find Object if available based on ID or Name - # ---------------------------------------------------- - try: - my_template = find_collection_item(aos.DesignTemplates, - item_name=item_name, - item_id=item_id) - except Exception: - module.fail_json(msg="Unable to find the IP Pool based on name or ID, something went wrong") - - # ---------------------------------------------------- - # Proceed based on State value - # ---------------------------------------------------- - if margs['state'] == 'absent': - - template_absent(module, aos, my_template) - - elif margs['state'] == 'present': - - template_present(module, aos, my_template) - - -def main(): - module = AnsibleModule( - argument_spec=dict( - session=dict(required=True, type="dict"), - name=dict(required=False), - id=dict(required=False), - content=dict(required=False, type="json"), - state=dict(required=False, - choices=['present', 'absent'], - default="present") - ), - mutually_exclusive=[('name', 'id', 'content')], - required_one_of=[('name', 'id', 'content')], - supports_check_mode=True - ) - - # Check if aos-pyez is present and match the minimum version - check_aos_version(module, '0.6.0') - - aos_template(module) +from ansible.module_utils.common.removed import removed_module -if __name__ == "__main__": - main() +if __name__ == '__main__': + removed_module(removed_in='2.9') diff --git a/lib/ansible/modules/network/nxos/_nxos_ip_interface.py b/lib/ansible/modules/network/nxos/_nxos_ip_interface.py index 5f87ec19bbe..636c9ad98ac 100644 --- a/lib/ansible/modules/network/nxos/_nxos_ip_interface.py +++ b/lib/ansible/modules/network/nxos/_nxos_ip_interface.py @@ -1,599 +1,14 @@ #!/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 . -# +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['deprecated'], + 'status': ['removed'], 'supported_by': 'network'} -DOCUMENTATION = ''' ---- -module: nxos_ip_interface -version_added: "2.1" -deprecated: - removed_in: "2.9" - why: Replaced with common C(*_l3_interface) network modules. - alternative: Use M(nxos_l3_interface) instead. -short_description: Manages L3 attributes for IPv4 and IPv6 interfaces. -description: - - Manages Layer 3 attributes for IPv4 and IPv6 interfaces. -extends_documentation_fragment: nxos -author: - - Jason Edelman (@jedelman8) - - Gabriele Gerbino (@GGabriele) -notes: - - Tested against NXOSv 7.3.(0)D1(1) on VIRL - - Interface must already be a L3 port when using this module. - - Logical interfaces (po, loop, svi) 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. - - C(tag) is not idempotent for IPv6 addresses and I2 system image. -options: - interface: - description: - - Full name of interface, i.e. Ethernet1/1, vlan10. - required: true - addr: - description: - - IPv4 or IPv6 Address. - version: - description: - - Version of IP address. If the IP address is IPV4 version should be v4. - If the IP address is IPV6 version should be v6. - default: v4 - choices: ['v4', 'v6'] - mask: - description: - - Subnet mask for IPv4 or IPv6 Address in decimal format. - dot1q: - description: - - Configures IEEE 802.1Q VLAN encapsulation on the subinterface. The range is from 2 to 4093. - version_added: "2.5" - tag: - description: - - Route tag for IPv4 or IPv6 Address in integer format. - default: 0 - version_added: "2.4" - allow_secondary: - description: - - Allow to configure IPv4 secondary addresses on interface. - type: bool - default: 'no' - version_added: "2.4" - state: - description: - - Specify desired state of the resource. - default: present - choices: ['present','absent'] -requirements: - - "ipaddress" -''' -EXAMPLES = ''' -- name: Ensure ipv4 address is configured on Ethernet1/32 - nxos_ip_interface: - interface: Ethernet1/32 - transport: nxapi - version: v4 - state: present - addr: 20.20.20.20 - mask: 24 - -- name: Ensure ipv6 address is configured on Ethernet1/31 - nxos_ip_interface: - interface: Ethernet1/31 - transport: cli - version: v6 - state: present - addr: '2001::db8:800:200c:cccb' - mask: 64 - -- name: Ensure ipv4 address is configured with tag - nxos_ip_interface: - interface: Ethernet1/32 - transport: nxapi - version: v4 - state: present - tag: 100 - addr: 20.20.20.20 - mask: 24 - -- name: Ensure ipv4 address is configured on sub-intf with dot1q encapsulation - nxos_ip_interface: - interface: Ethernet1/32.10 - transport: nxapi - version: v4 - state: present - dot1q: 10 - addr: 20.20.20.20 - mask: 24 - -- name: Configure ipv4 address as secondary if needed - nxos_ip_interface: - interface: Ethernet1/32 - transport: nxapi - version: v4 - state: present - allow_secondary: true - addr: 21.21.21.21 - mask: 24 -''' - -RETURN = ''' -proposed: - description: k/v pairs of parameters passed into module - returned: always - type: dict - sample: {"addr": "20.20.20.20", "allow_secondary": true, - "interface": "Ethernet1/32", "mask": "24", "tag": 100} -existing: - description: k/v pairs of existing IP attributes on the interface - returned: always - type: dict - sample: {"addresses": [{"addr": "11.11.11.11", "mask": 17, "tag": 101, "secondary": false}], - "interface": "ethernet1/32", "prefixes": ["11.11.0.0/17"], - "type": "ethernet", "vrf": "default"} -end_state: - description: k/v pairs of IP attributes after module execution - returned: always - type: dict - sample: {"addresses": [{"addr": "11.11.11.11", "mask": 17, "tag": 101, "secondary": false}, - {"addr": "20.20.20.20", "mask": 24, "tag": 100, "secondary": true}], - "interface": "ethernet1/32", "prefixes": ["11.11.0.0/17", "20.20.20.0/24"], - "type": "ethernet", "vrf": "default"} -commands: - description: commands sent to the device - returned: always - type: list - sample: ["interface ethernet1/32", "ip address 20.20.20.20/24 secondary tag 100"] -changed: - description: check to see if a change was made on the device - returned: always - type: bool - sample: true -''' - -import re - -try: - import ipaddress - - HAS_IPADDRESS = True -except ImportError: - HAS_IPADDRESS = False - -from ansible.module_utils.network.nxos.nxos import load_config, run_commands -from ansible.module_utils.network.nxos.nxos import get_capabilities, nxos_argument_spec -from ansible.module_utils.network.nxos.nxos import get_interface_type -from ansible.module_utils.basic import AnsibleModule - - -def find_same_addr(existing, addr, mask, full=False, **kwargs): - for address in existing['addresses']: - if address['addr'] == addr and address['mask'] == mask: - if full: - if kwargs['version'] == 'v4' and int(address['tag']) == kwargs['tag']: - return address - elif kwargs['version'] == 'v6' and kwargs['tag'] == 0: - # Currently we don't get info about IPv6 address tag - # But let's not break idempotence for the default case - return address - else: - return address - return False - - -def execute_show_command(command, module): - cmd = {} - cmd['answer'] = None - cmd['command'] = command - cmd['output'] = 'text' - cmd['prompt'] = None - - body = run_commands(module, [cmd]) - - return body - - -def is_default(interface, module): - command = 'show run interface {0}'.format(interface) - - try: - body = execute_show_command(command, module)[0] - if 'invalid' in body.lower(): - return 'DNE' - else: - raw_list = body.split('\n') - if raw_list[-1].startswith('interface'): - return True - else: - return False - except KeyError: - return 'DNE' - - -def get_interface_mode(interface, intf_type, module): - command = 'show interface {0} switchport'.format(interface) - mode = 'unknown' - - if intf_type in ['ethernet', 'portchannel']: - body = execute_show_command(command, module)[0] - if len(body) > 0: - if 'Switchport: Disabled' in body: - mode = 'layer3' - elif 'Switchport: Enabled' in body: - mode = "layer2" - elif intf_type == 'svi': - mode = 'layer3' - return mode - - -def send_show_command(interface_name, version, module): - if version == 'v4': - command = 'show ip interface {0}'.format(interface_name) - elif version == 'v6': - command = 'show ipv6 interface {0}'.format(interface_name) - body = execute_show_command(command, module) - return body - - -def parse_unstructured_data(body, interface_name, version, module): - interface = {} - interface['addresses'] = [] - interface['prefixes'] = [] - vrf = None - - body = body[0] - splitted_body = body.split('\n') - - if version == "v6": - if "ipv6 is disabled" not in body.lower(): - address_list = [] - # We can have multiple IPv6 on the same interface. - # We need to parse them manually from raw output. - for index in range(0, len(splitted_body) - 1): - if "IPv6 address:" in splitted_body[index]: - first_reference_point = index + 1 - elif "IPv6 subnet:" in splitted_body[index]: - last_reference_point = index - break - - interface_list_table = splitted_body[first_reference_point:last_reference_point] - - for each_line in interface_list_table: - address = each_line.strip().split(' ')[0] - if address not in address_list: - address_list.append(address) - interface['prefixes'].append(str(ipaddress.ip_interface(u"%s" % address).network)) - - if address_list: - for ipv6 in address_list: - address = {} - splitted_address = ipv6.split('/') - address['addr'] = splitted_address[0] - address['mask'] = splitted_address[1] - interface['addresses'].append(address) - - else: - for index in range(0, len(splitted_body) - 1): - if "IP address" in splitted_body[index]: - regex = r'.*IP\saddress:\s(?P\d{1,3}(?:\.\d{1,3}){3}),\sIP\ssubnet:' + \ - r'\s\d{1,3}(?:\.\d{1,3}){3}\/(?P\d+)(?:\s(?Psecondary)\s)?' + \ - r'(.+?tag:\s(?P\d+).*)?' - match = re.match(regex, splitted_body[index]) - if match: - match_dict = match.groupdict() - if match_dict['secondary'] is None: - match_dict['secondary'] = False - else: - match_dict['secondary'] = True - if match_dict['tag'] is None: - match_dict['tag'] = 0 - else: - match_dict['tag'] = int(match_dict['tag']) - interface['addresses'].append(match_dict) - prefix = str(ipaddress.ip_interface(u"%(addr)s/%(mask)s" % match_dict).network) - interface['prefixes'].append(prefix) - - try: - vrf_regex = r'.+?VRF\s+(?P\S+?)\s' - match_vrf = re.match(vrf_regex, body, re.DOTALL) - vrf = match_vrf.groupdict()['vrf'] - except AttributeError: - vrf = None - - interface['interface'] = interface_name - interface['type'] = get_interface_type(interface_name) - interface['vrf'] = vrf - - return interface - - -def parse_interface_data(body): - body = body[0] - splitted_body = body.split('\n') - - for index in range(0, len(splitted_body) - 1): - if "Encapsulation 802.1Q" in splitted_body[index]: - regex = r'(.+?ID\s(?P\d+).*)?' - match = re.match(regex, splitted_body[index]) - if match: - match_dict = match.groupdict() - if match_dict['dot1q'] is not None: - return int(match_dict['dot1q']) - return 0 - - -def get_dot1q_id(interface_name, module): - - if "." not in interface_name: - return 0 - - command = 'show interface {0}'.format(interface_name) - try: - body = execute_show_command(command, module) - dot1q = parse_interface_data(body) - return dot1q - except KeyError: - return 0 - - -def get_ip_interface(interface_name, version, module): - body = send_show_command(interface_name, version, module) - interface = parse_unstructured_data(body, interface_name, version, module) - return interface - - -def get_remove_ip_config_commands(interface, addr, mask, existing, version): - commands = [] - if version == 'v4': - # We can't just remove primary address if secondary address exists - for address in existing['addresses']: - if address['addr'] == addr: - if address['secondary']: - commands.append('no ip address {0}/{1} secondary'.format(addr, mask)) - elif len(existing['addresses']) > 1: - new_primary = False - for address in existing['addresses']: - if address['addr'] != addr: - commands.append('no ip address {0}/{1} secondary'.format(address['addr'], address['mask'])) - - if not new_primary: - command = 'ip address {0}/{1}'.format(address['addr'], address['mask']) - new_primary = True - else: - command = 'ip address {0}/{1} secondary'.format(address['addr'], address['mask']) - - if 'tag' in address and address['tag'] != 0: - command += " tag " + str(address['tag']) - commands.append(command) - else: - commands.append('no ip address {0}/{1}'.format(addr, mask)) - break - else: - for address in existing['addresses']: - if address['addr'] == addr: - commands.append('no ipv6 address {0}/{1}'.format(addr, mask)) - - return commands - - -def get_config_ip_commands(delta, interface, existing, version): - commands = [] - delta = dict(delta) - - if version == 'v4': - command = 'ip address {addr}/{mask}'.format(**delta) - if len(existing['addresses']) > 0: - if delta['allow_secondary']: - for address in existing['addresses']: - if delta['addr'] == address['addr'] and address['secondary'] is False and delta['tag'] != 0: - break - else: - command += ' secondary' - else: - # Remove all existed addresses if 'allow_secondary' isn't specified - for address in existing['addresses']: - if address['secondary']: - commands.insert(0, 'no ip address {addr}/{mask} secondary'.format(**address)) - else: - commands.append('no ip address {addr}/{mask}'.format(**address)) - else: - if not delta['allow_secondary']: - # Remove all existed addresses if 'allow_secondary' isn't specified - for address in existing['addresses']: - commands.insert(0, 'no ipv6 address {addr}/{mask}'.format(**address)) - - command = 'ipv6 address {addr}/{mask}'.format(**delta) - - if int(delta['tag']) > 0: - command += ' tag {tag}'.format(**delta) - elif int(delta['tag']) == 0: - # Case when we need to remove tag from an address. Just enter command like - # 'ip address ...' (without 'tag') not enough - commands += get_remove_ip_config_commands(interface, delta['addr'], delta['mask'], existing, version) - - commands.append(command) - return commands - - -def flatten_list(command_lists): - flat_command_list = [] - for command in command_lists: - if isinstance(command, list): - flat_command_list.extend(command) - else: - flat_command_list.append(command) - return flat_command_list - - -def validate_params(addr, interface, mask, dot1q, tag, allow_secondary, version, state, intf_type, module): - device_info = get_capabilities(module) - network_api = device_info.get('network_api', 'nxapi') - - if state == "present": - if addr is None or mask is None: - module.fail_json(msg="An IP address AND a mask must be provided " - "when state=present.") - elif state == "absent" and version == "v6": - if addr is None or mask is None: - module.fail_json(msg="IPv6 address and mask must be provided when " - "state=absent.") - - if intf_type != "ethernet" and network_api == 'cliconf': - if is_default(interface, module) == "DNE": - module.fail_json(msg="That interface does not exist yet. Create " - "it first.", interface=interface) - if mask is not None: - try: - if (int(mask) < 1 or int(mask) > 32) and version == "v4": - raise ValueError - elif int(mask) < 1 or int(mask) > 128: - raise ValueError - except ValueError: - module.fail_json(msg="Warning! 'mask' must be an integer between" - " 1 and 32 when version v4 and up to 128 " - "when version v6.", version=version, - mask=mask) - if addr is not None and mask is not None: - try: - ipaddress.ip_interface(u'%s/%s' % (addr, mask)) - except ValueError: - module.fail_json(msg="Warning! Invalid ip address or mask set.", addr=addr, mask=mask) - - if dot1q is not None: - try: - if 2 > dot1q > 4093: - raise ValueError - except ValueError: - module.fail_json(msg="Warning! 'dot1q' must be an integer between" - " 2 and 4093", dot1q=dot1q) - if tag is not None: - try: - if 0 > tag > 4294967295: - raise ValueError - except ValueError: - module.fail_json(msg="Warning! 'tag' must be an integer between" - " 0 (default) and 4294967295." - "To use tag you must set 'addr' and 'mask' params.", tag=tag) - if allow_secondary is not None: - try: - if addr is None or mask is None: - raise ValueError - except ValueError: - module.fail_json(msg="Warning! 'secondary' can be used only when 'addr' and 'mask' set.", - allow_secondary=allow_secondary) - - -def 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), - dot1q=dict(required=False, default=0, type='int'), - tag=dict(required=False, default=0, type='int'), - state=dict(required=False, default='present', - choices=['present', 'absent']), - allow_secondary=dict(required=False, default=False, - type='bool') - ) - - argument_spec.update(nxos_argument_spec) - - module = AnsibleModule(argument_spec=argument_spec, - supports_check_mode=True) - - if not HAS_IPADDRESS: - module.fail_json(msg="ipaddress is required for this module. Run 'pip install ipaddress' for install.") - - warnings = list() - - addr = module.params['addr'] - version = module.params['version'] - mask = module.params['mask'] - dot1q = module.params['dot1q'] - tag = module.params['tag'] - allow_secondary = module.params['allow_secondary'] - interface = module.params['interface'].lower() - state = module.params['state'] - - intf_type = get_interface_type(interface) - validate_params(addr, interface, mask, dot1q, tag, allow_secondary, version, state, intf_type, module) - - mode = get_interface_mode(interface, intf_type, module) - if mode == 'layer2': - module.fail_json(msg='That interface is a layer2 port.\nMake it ' - 'a layer 3 port first.', interface=interface) - - existing = get_ip_interface(interface, version, module) - - dot1q_tag = get_dot1q_id(interface, module) - if dot1q_tag > 1: - existing['dot1q'] = dot1q_tag - - args = dict(addr=addr, mask=mask, dot1q=dot1q, tag=tag, interface=interface, allow_secondary=allow_secondary) - proposed = dict((k, v) for k, v in args.items() if v is not None) - commands = [] - changed = False - end_state = existing - - commands = ['interface {0}'.format(interface)] - if state == 'absent': - if existing['addresses']: - if find_same_addr(existing, addr, mask): - command = get_remove_ip_config_commands(interface, addr, - mask, existing, version) - commands.append(command) - if 'dot1q' in existing and existing['dot1q'] > 1: - command = 'no encapsulation dot1Q {0}'.format(existing['dot1q']) - commands.append(command) - elif state == 'present': - if not find_same_addr(existing, addr, mask, full=True, tag=tag, version=version): - command = get_config_ip_commands(proposed, interface, existing, version) - commands.append(command) - if 'dot1q' not in existing and (intf_type in ['ethernet', 'portchannel'] and "." in interface): - command = 'encapsulation dot1Q {0}'.format(proposed['dot1q']) - commands.append(command) - if len(commands) < 2: - del commands[0] - cmds = flatten_list(commands) - if cmds: - if module.check_mode: - module.exit_json(changed=True, commands=cmds) - else: - load_config(module, cmds) - changed = True - end_state = get_ip_interface(interface, version, module) - if 'configure' in cmds: - cmds.pop(0) - - results = {} - results['proposed'] = proposed - results['existing'] = existing - results['end_state'] = end_state - results['commands'] = cmds - results['changed'] = changed - results['warnings'] = warnings - - module.exit_json(**results) +from ansible.module_utils.common.removed import removed_module if __name__ == '__main__': - main() + removed_module(removed_in='2.9') diff --git a/lib/ansible/modules/network/nxos/_nxos_portchannel.py b/lib/ansible/modules/network/nxos/_nxos_portchannel.py index 838dbd65f7a..636c9ad98ac 100644 --- a/lib/ansible/modules/network/nxos/_nxos_portchannel.py +++ b/lib/ansible/modules/network/nxos/_nxos_portchannel.py @@ -1,480 +1,14 @@ #!/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 . -# +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['deprecated'], + 'status': ['removed'], 'supported_by': 'network'} -DOCUMENTATION = ''' ---- -module: nxos_portchannel -extends_documentation_fragment: nxos -version_added: "2.2" -deprecated: - removed_in: "2.9" - why: Replaced with common C(*_linkagg) network modules. - alternative: Use M(nxos_linkagg) instead. -short_description: Manages port-channel interfaces. -description: - - Manages port-channel specific configuration parameters. -author: - - Jason Edelman (@jedelman8) - - Gabriele Gerbino (@GGabriele) -notes: - - Tested against NXOSv 7.3.(0)D1(1) on VIRL - - C(state=absent) removes the portchannel config and interface if it - already exists. If members to be removed are not explicitly - passed, all existing members (if any), are removed. - - Members must be a list. - - LACP needs to be enabled first if active/passive modes are used. -options: - group: - description: - - Channel-group number for the port-channel. - required: true - mode: - description: - - Mode for the port-channel, i.e. on, active, passive. - default: on - choices: ['active','passive','on'] - min_links: - description: - - Min links required to keep portchannel up. - members: - description: - - List of interfaces that will be managed in a given portchannel. - force: - description: - - When true it forces port-channel members to match what is - declared in the members param. This can be used to remove - members. - choices: [ 'false', 'true' ] - default: 'false' - state: - description: - - Manage the state of the resource. - default: present - choices: ['present','absent'] -''' -EXAMPLES = ''' -# Ensure port-channel99 is created, add two members, and set to mode on -- nxos_portchannel: - group: 99 - members: ['Ethernet1/1','Ethernet1/2'] - mode: 'active' - state: present -''' - -RETURN = ''' -commands: - description: command sent to the device - returned: always - type: list - sample: ["interface Ethernet2/6", "no channel-group 12", - "interface Ethernet2/5", "no channel-group 12", - "interface Ethernet2/6", "channel-group 12 mode on", - "interface Ethernet2/5", "channel-group 12 mode on"] -''' - -import collections -import re - -from ansible.module_utils.network.nxos.nxos import get_config, load_config, run_commands -from ansible.module_utils.network.nxos.nxos import get_capabilities, nxos_argument_spec -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.network.common.config import CustomNetworkConfig - - -def get_value(arg, config, module): - param_to_command_keymap = { - 'min_links': 'lacp min-links' - } - - REGEX = re.compile(r'(?:{0}\s)(?P.*)$'.format(param_to_command_keymap[arg]), re.M) - value = '' - if param_to_command_keymap[arg] in config: - value = REGEX.search(config).group('value') - return value - - -def check_interface(module, netcfg): - config = str(netcfg) - REGEX = re.compile(r'\s+interface port-channel{0}$'.format(module.params['group']), re.M) - value = False - try: - if REGEX.search(config): - value = True - except TypeError: - value = False - - return value - - -def get_custom_value(arg, config, module): - REGEX = re.compile(r'\s+member vni {0} associate-vrf\s*$'.format( - module.params['vni']), re.M) - value = False - try: - if REGEX.search(config): - value = True - except TypeError: - value = False - return value - - -def get_portchannel_members(pchannel): - try: - members = pchannel['TABLE_member']['ROW_member'] - except KeyError: - members = [] - - return members - - -def get_portchannel_mode(interface, protocol, module, netcfg): - if protocol != 'LACP': - mode = 'on' - else: - netcfg = CustomNetworkConfig(indent=2, contents=get_config(module)) - parents = ['interface {0}'.format(interface.capitalize())] - body = netcfg.get_section(parents) - - mode_list = body.split('\n') - - for line in mode_list: - this_line = line.strip() - if this_line.startswith('channel-group'): - find = this_line - if 'mode' in find: - if 'passive' in find: - mode = 'passive' - elif 'active' in find: - mode = 'active' - - return mode - - -def get_portchannel(module, netcfg=None): - command = 'show port-channel summary | json' - portchannel = {} - portchannel_table = {} - members = [] - - try: - body = run_commands(module, [command])[0] - pc_table = body['TABLE_channel']['ROW_channel'] - - if isinstance(pc_table, dict): - pc_table = [pc_table] - - for pc in pc_table: - if pc['group'] == module.params['group']: - portchannel_table = pc - elif module.params['group'].isdigit() and pc['group'] == int(module.params['group']): - portchannel_table = pc - except (KeyError, AttributeError, TypeError, IndexError): - return {} - - if portchannel_table: - portchannel['group'] = portchannel_table['group'] - protocol = portchannel_table['prtcl'] - members_list = get_portchannel_members(portchannel_table) - - if isinstance(members_list, dict): - members_list = [members_list] - - member_dictionary = {} - for each_member in members_list: - interface = each_member['port'] - members.append(interface) - - pc_member = {} - pc_member['status'] = str(each_member['port-status']) - pc_member['mode'] = get_portchannel_mode(interface, - protocol, module, netcfg) - - member_dictionary[interface] = pc_member - portchannel['members'] = members - portchannel['members_detail'] = member_dictionary - - # Ensure each member have the same mode. - modes = set() - for each, value in member_dictionary.items(): - modes.update([value['mode']]) - if len(modes) == 1: - portchannel['mode'] = value['mode'] - else: - portchannel['mode'] = 'unknown' - return portchannel - - -def get_existing(module, args): - existing = {} - netcfg = CustomNetworkConfig(indent=2, contents=get_config(module)) - - interface_exist = check_interface(module, netcfg) - if interface_exist: - parents = ['interface port-channel{0}'.format(module.params['group'])] - config = netcfg.get_section(parents) - - if config: - existing['min_links'] = get_value('min_links', config, module) - existing.update(get_portchannel(module, netcfg=netcfg)) - - return existing, interface_exist - - -def config_portchannel(proposed, mode, group, force): - commands = [] - # NOTE: Leading whitespace for force option is important - force = ' force' if force else '' - config_args = { - 'mode': 'channel-group {group}{force} mode {mode}', - 'min_links': 'lacp min-links {min_links}', - } - - for member in proposed.get('members', []): - commands.append('interface {0}'.format(member)) - commands.append(config_args.get('mode').format(group=group, force=force, mode=mode)) - - min_links = proposed.get('min_links', None) - if min_links: - command = 'interface port-channel {0}'.format(group) - commands.append(command) - commands.append(config_args.get('min_links').format( - min_links=min_links)) - - return commands - - -def get_commands_to_add_members(proposed, existing, force, module): - try: - proposed_members = proposed['members'] - except KeyError: - proposed_members = [] - - try: - existing_members = existing['members'] - except KeyError: - existing_members = [] - - members_to_add = list(set(proposed_members).difference(existing_members)) - - commands = [] - # NOTE: Leading whitespace for force option is important - force = ' force' if force else '' - if members_to_add: - for member in members_to_add: - commands.append('interface {0}'.format(member)) - commands.append('channel-group {0}{1} mode {2}'.format( - existing['group'], force, proposed['mode'])) - - return commands - - -def get_commands_to_remove_members(proposed, existing, module): - try: - proposed_members = proposed['members'] - except KeyError: - proposed_members = [] - - try: - existing_members = existing['members'] - except KeyError: - existing_members = [] - - members_to_remove = list(set(existing_members).difference(proposed_members)) - commands = [] - if members_to_remove: - for member in members_to_remove: - commands.append('interface {0}'.format(member)) - commands.append('no channel-group {0}'.format(existing['group'])) - - return commands - - -def get_commands_if_mode_change(proposed, existing, group, mode, force, module): - try: - proposed_members = proposed['members'] - except KeyError: - proposed_members = [] - - try: - existing_members = existing['members'] - except KeyError: - existing_members = [] - - try: - members_dict = existing['members_detail'] - except KeyError: - members_dict = {} - - members_to_remove = set(existing_members).difference(proposed_members) - members_with_mode_change = [] - if members_dict: - for interface, values in members_dict.items(): - if (interface in proposed_members and - (interface not in members_to_remove)): - if values['mode'] != mode: - members_with_mode_change.append(interface) - - commands = [] - # NOTE: Leading whitespace for force option is important - force = ' force' if force else '' - if members_with_mode_change: - for member in members_with_mode_change: - commands.append('interface {0}'.format(member)) - commands.append('no channel-group {0}'.format(group)) - - for member in members_with_mode_change: - commands.append('interface {0}'.format(member)) - commands.append('channel-group {0}{1} mode {2}'.format(group, force, mode)) - - return commands - - -def get_commands_min_links(existing, proposed, group, min_links, module): - commands = [] - try: - if (existing['min_links'] is None or - (existing['min_links'] != proposed['min_links'])): - commands.append('interface port-channel{0}'.format(group)) - commands.append('lacp min-link {0}'.format(min_links)) - except KeyError: - commands.append('interface port-channel{0}'.format(group)) - commands.append('lacp min-link {0}'.format(min_links)) - return commands - - -def flatten_list(command_lists): - flat_command_list = [] - for command in command_lists: - if isinstance(command, list): - flat_command_list.extend(command) - else: - flat_command_list.append(command) - return flat_command_list - - -def state_present(module, existing, proposed, interface_exist, force, warnings): - commands = [] - group = str(module.params['group']) - mode = module.params['mode'] - min_links = module.params['min_links'] - - if not interface_exist: - command = config_portchannel(proposed, mode, group, force) - commands.append(command) - commands.insert(0, 'interface port-channel{0}'.format(group)) - warnings.append("The proposed port-channel interface did not " - "exist. It's recommended to use nxos_interface to " - "create all logical interfaces.") - - elif existing and interface_exist: - if force: - command = get_commands_to_remove_members(proposed, existing, module) - commands.append(command) - - command = get_commands_to_add_members(proposed, existing, force, module) - commands.append(command) - - mode_command = get_commands_if_mode_change(proposed, existing, group, mode, force, module) - commands.insert(0, mode_command) - - if min_links: - command = get_commands_min_links(existing, proposed, group, min_links, module) - commands.append(command) - - return commands - - -def state_absent(module, existing, proposed): - commands = [] - group = str(module.params['group']) - commands.append(['no interface port-channel{0}'.format(group)]) - return commands - - -def main(): - argument_spec = dict( - group=dict(required=True, type='str'), - mode=dict(required=False, choices=['on', 'active', 'passive'], default='on', type='str'), - min_links=dict(required=False, default=None, type='str'), - members=dict(required=False, default=None, type='list'), - force=dict(required=False, default='false', type='str', choices=['true', 'false']), - state=dict(required=False, choices=['absent', 'present'], default='present'), - ) - - argument_spec.update(nxos_argument_spec) - - module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) - - warnings = list() - results = dict(changed=False, warnings=warnings) - - group = str(module.params['group']) - mode = module.params['mode'] - min_links = module.params['min_links'] - members = module.params['members'] - state = module.params['state'] - - if str(module.params['force']).lower() == 'true': - force = True - elif module.params['force'] == 'false': - force = False - - if ((min_links or mode) and - (not members and state == 'present')): - module.fail_json(msg='"members" is required when state=present and ' - '"min_links" or "mode" are provided') - - args = [ - 'group', - 'members', - 'min_links', - 'mode' - ] - - existing, interface_exist = get_existing(module, args) - proposed = dict((k, v) for k, v in module.params.items() - if v is not None and k in args) - - commands = [] - - if state == 'absent' and existing: - commands = state_absent(module, existing, proposed) - elif state == 'present': - commands = state_present(module, existing, proposed, interface_exist, force, warnings) - - cmds = flatten_list(commands) - if cmds: - if module.check_mode: - module.exit_json(**results) - else: - load_config(module, cmds) - results['changed'] = True - if 'configure' in cmds: - cmds.pop(0) - - results['commands'] = cmds - module.exit_json(**results) +from ansible.module_utils.common.removed import removed_module if __name__ == '__main__': - main() + removed_module(removed_in='2.9') diff --git a/lib/ansible/modules/network/nxos/_nxos_switchport.py b/lib/ansible/modules/network/nxos/_nxos_switchport.py index 6f798b13c97..636c9ad98ac 100644 --- a/lib/ansible/modules/network/nxos/_nxos_switchport.py +++ b/lib/ansible/modules/network/nxos/_nxos_switchport.py @@ -1,542 +1,14 @@ #!/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 . -# +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['deprecated'], + 'status': ['removed'], 'supported_by': 'network'} -DOCUMENTATION = ''' ---- -module: nxos_switchport -extends_documentation_fragment: nxos -version_added: "2.1" -deprecated: - removed_in: "2.9" - why: Replaced with generic version. - alternative: Use M(nxos_l2_interface) instead. -short_description: Manages Layer 2 switchport interfaces. -description: - - Manages Layer 2 interfaces -author: Jason Edelman (@jedelman8) -notes: - - Tested against NXOSv 7.3.(0)D1(1) on VIRL - - 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 `switchport trunk allowed 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. -options: - interface: - description: - - Full name of the interface, i.e. Ethernet1/1. - mode: - description: - - Mode for the Layer 2 port. - choices: ['access','trunk'] - access_vlan: - description: - - If C(mode=access), used as the access VLAN ID. - native_vlan: - description: - - If C(mode=trunk), used as the trunk native VLAN ID. - trunk_vlans: - description: - - If C(mode=trunk), used as the VLAN range to ADD or REMOVE - from the trunk. - aliases: - - trunk_add_vlans - state: - description: - - Manage the state of the resource. - default: present - choices: ['present','absent', 'unconfigured'] - trunk_allowed_vlans: - description: - - if C(mode=trunk), these are the only VLANs that will be - configured on the trunk, i.e. "2-10,15". - version_added: 2.2 -''' -EXAMPLES = ''' -- name: Ensure Eth1/5 is in its default switchport state - nxos_switchport: - interface: eth1/5 - state: unconfigured - -- name: Ensure Eth1/5 is configured for access vlan 20 - nxos_switchport: - interface: eth1/5 - mode: access - access_vlan: 20 - -- name: Ensure Eth1/5 only has vlans 5-10 as trunk vlans - nxos_switchport: - interface: eth1/5 - mode: trunk - native_vlan: 10 - trunk_vlans: 5-10 - -- name: Ensure eth1/5 is a trunk port and ensure 2-50 are being tagged (doesn't mean others aren't also being tagged) - nxos_switchport: - interface: eth1/5 - mode: trunk - native_vlan: 10 - trunk_vlans: 2-50 - -- name: Ensure these VLANs are not being tagged on the trunk - nxos_switchport: - interface: eth1/5 - mode: trunk - trunk_vlans: 51-4094 - state: absent -''' - -RETURN = ''' -commands: - description: command string sent to the device - returned: always - type: list - sample: ["interface eth1/5", "switchport access vlan 20"] -''' - -from ansible.module_utils.network.nxos.nxos import load_config, run_commands -from ansible.module_utils.network.nxos.nxos import get_capabilities, nxos_argument_spec -from ansible.module_utils.network.nxos.nxos import get_interface_type -from ansible.module_utils.basic import AnsibleModule - - -def get_interface_mode(interface, module): - """Gets current mode of interface: layer2 or layer3 - Args: - device (Device): This is the device object of an NX-API enabled device - using the Device class within device.py - interface (string): full name of interface, i.e. Ethernet1/1, - loopback10, port-channel20, vlan20 - Returns: - str: 'layer2' or 'layer3' - """ - command = 'show interface {0} | json'.format(interface) - intf_type = get_interface_type(interface) - mode = 'unknown' - interface_table = {} - - try: - body = run_commands(module, [command])[0] - interface_table = body['TABLE_interface']['ROW_interface'] - except (KeyError, AttributeError, IndexError): - return mode - - if interface_table: - # HACK FOR NOW - if intf_type in ['ethernet', 'portchannel']: - mode = str(interface_table.get('eth_mode', 'layer3')) - if mode in ['access', 'trunk']: - mode = 'layer2' - if mode == 'routed': - mode = 'layer3' - elif intf_type == 'loopback' or intf_type == 'svi': - mode = 'layer3' - return mode - - -def interface_is_portchannel(interface, module): - """Checks to see if an interface is part of portchannel bundle - Args: - interface (str): full name of interface, i.e. Ethernet1/1 - Returns: - True/False based on if interface is a member of a portchannel bundle - """ - intf_type = get_interface_type(interface) - - if intf_type == 'ethernet': - command = 'show interface {0} | json'.format(interface) - try: - body = run_commands(module, [command])[0] - interface_table = body['TABLE_interface']['ROW_interface'] - except (KeyError, AttributeError, IndexError): - interface_table = None - - if interface_table: - state = interface_table.get('eth_bundle') - if state: - return True - else: - return False - - return False - - -def get_switchport(port, module): - """Gets current config of L2 switchport - Args: - device (Device): This is the device object of an NX-API enabled device - using the Device class within device.py - port (str): full name of interface, i.e. Ethernet1/1 - Returns: - dictionary with k/v pairs for L2 vlan config - """ - - command = 'show interface {0} switchport | json'.format(port) - - try: - body = run_commands(module, [command])[0] - sp_table = body['TABLE_interface']['ROW_interface'] - except (KeyError, AttributeError, IndexError): - sp_table = None - - if sp_table: - key_map = { - "interface": "interface", - "oper_mode": "mode", - "switchport": "switchport", - "access_vlan": "access_vlan", - "access_vlan_name": "access_vlan_name", - "native_vlan": "native_vlan", - "native_vlan_name": "native_vlan_name", - "trunk_vlans": "trunk_vlans" - } - sp = apply_key_map(key_map, sp_table) - return sp - - else: - return {} - - -def remove_switchport_config_commands(interface, 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': - - # Supported Remove Scenarios for trunk_vlans_list - # 1) Existing: 1,2,3 Proposed: 1,2,3 - Remove all - # 2) Existing: 1,2,3 Proposed: 1,2 - Remove 1,2 Leave 3 - # 3) Existing: 1,2,3 Proposed: 2,3 - Remove 2,3 Leave 1 - # 4) Existing: 1,2,3 Proposed: 4,5,6 - None removed. - # 5) Existing: None Proposed: 1,2,3 - None removed. - - existing_vlans = existing.get('trunk_vlans_list') - proposed_vlans = proposed.get('trunk_vlans_list') - vlans_to_remove = set(proposed_vlans).intersection(existing_vlans) - - if vlans_to_remove: - proposed_allowed_vlans = proposed.get('trunk_allowed_vlans') - remove_trunk_allowed_vlans = proposed.get('trunk_vlans', proposed_allowed_vlans) - command = 'switchport trunk allowed vlan remove {0}'.format(remove_trunk_allowed_vlans) - commands.append(command) - - native_check = existing.get('native_vlan') == proposed.get('native_vlan') - if native_check and proposed.get('native_vlan'): - command = 'no switchport trunk native vlan {0}'.format(existing.get('native_vlan')) - commands.append(command) - - if commands: - commands.insert(0, 'interface ' + interface) - return commands - - -def get_switchport_config_commands(interface, 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}'.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 ' + interface) - 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(interface): - commands = [] - commands.append('interface ' + interface) - 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: - a, b = part.split('-') - a, b = int(a), int(b) - result.extend(range(a, b + 1)) - else: - a = int(part) - result.append(a) - return numerical_sort(result) - return result - - -def get_list_of_vlans(module): - - command = 'show vlan | json' - vlan_list = [] - - try: - body = run_commands(module, [command])[0] - vlan_table = body['TABLE_vlanbrief']['ROW_vlanbrief'] - except (KeyError, AttributeError, IndexError): - return [] - - if isinstance(vlan_table, list): - for vlan in vlan_table: - vlan_list.append(str(vlan['vlanshowbr-vlanid-utf'])) - else: - vlan_list.append('1') - - return vlan_list - - -def numerical_sort(string_int_list): - """Sorts list of strings/integers that are digits in numerical order. - """ - - as_int_list = [] - as_str_list = [] - for vlan in string_int_list: - as_int_list.append(int(vlan)) - as_int_list.sort() - for vlan in as_int_list: - as_str_list.append(str(vlan)) - return as_str_list - - -def apply_key_map(key_map, table): - new_dict = {} - for key, value in table.items(): - new_key = key_map.get(key) - if new_key: - new_dict[new_key] = value - return new_dict - - -def apply_value_map(value_map, resource): - for key, value in value_map.items(): - resource[key] = value[resource.get(key)] - return resource - - -def flatten_list(command_lists): - flat_command_list = [] - for command in command_lists: - if isinstance(command, list): - flat_command_list.extend(command) - else: - flat_command_list.append(command) - return flat_command_list - - -def main(): - - argument_spec = dict( - interface=dict(required=True, type='str'), - mode=dict(choices=['access', 'trunk'], required=False), - access_vlan=dict(type='str', required=False), - native_vlan=dict(type='str', required=False), - trunk_vlans=dict(type='str', aliases=['trunk_add_vlans'], required=False), - trunk_allowed_vlans=dict(type='str', required=False), - state=dict(choices=['absent', 'present', 'unconfigured'], default='present') - ) - - argument_spec.update(nxos_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 = [] - results = {'changed': False} - - interface = module.params['interface'] - mode = module.params['mode'] - access_vlan = module.params['access_vlan'] - state = module.params['state'] - trunk_vlans = module.params['trunk_vlans'] - native_vlan = module.params['native_vlan'] - trunk_allowed_vlans = module.params['trunk_allowed_vlans'] - - args = dict(interface=interface, 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) - - interface = interface.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') - - current_mode = get_interface_mode(interface, module) - - # Current mode will return layer3, layer2, or unknown - if current_mode == 'unknown' or current_mode == 'layer3': - module.fail_json(msg='Ensure interface is configured to be a L2' - '\nport first before using this module. You can use' - '\nthe nxos_interface module for this.') - - if interface_is_portchannel(interface, 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(interface, 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(interface, existing, proposed, module) - commands.append(command) - elif state == 'unconfigured': - is_default = is_switchport_default(existing) - if not is_default: - command = default_switchport_config(interface) - commands.append(command) - elif state == 'absent': - command = remove_switchport_config_commands(interface, 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: - results['changed'] = True - load_config(module, cmds) - if 'configure' in cmds: - cmds.pop(0) - - results['commands'] = cmds - results['warnings'] = warnings - - module.exit_json(**results) +from ansible.module_utils.common.removed import removed_module if __name__ == '__main__': - main() + removed_module(removed_in='2.9') diff --git a/lib/ansible/modules/network/panos/_panos_nat_policy.py b/lib/ansible/modules/network/panos/_panos_nat_policy.py index a29f3a99b2e..45c6615b96d 100644 --- a/lib/ansible/modules/network/panos/_panos_nat_policy.py +++ b/lib/ansible/modules/network/panos/_panos_nat_policy.py @@ -3,335 +3,15 @@ # # 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 . +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['deprecated'], + 'status': ['removed'], 'supported_by': 'community'} -DOCUMENTATION = ''' ---- -module: panos_nat_policy -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)" -version_added: "2.3" -requirements: - - pan-python -deprecated: - alternative: Use M(panos_nat_rule) instead. - removed_in: '2.9' - why: This module depended on outdated and old SDK, use M(panos_nat_rule) instead. -options: - ip_address: - description: - - IP address (or hostname) of PAN-OS device - required: true - password: - description: - - password for authentication - required: true - username: - description: - - username for authentication - default: "admin" - rule_name: - description: - - name of the SNAT rule - required: true - from_zone: - description: - - list of source zones - required: true - to_zone: - description: - - destination zone - required: true - source: - description: - - list of source addresses - default: ["any"] - destination: - description: - - list of destination addresses - default: ["any"] - service: - description: - - service - default: "any" - snat_type: - description: - - type of source translation - snat_address: - description: - - snat translated address - 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 - override: - description: - - attempt to override rule if one with the same name already exists - type: bool - default: 'no' - commit: - description: - - commit if changed - type: bool - default: 'yes' -''' - -EXAMPLES = ''' -# Create a source and destination nat rule - - name: create nat SSH221 rule for 10.0.1.101 - panos_nat: - ip_address: "192.168.1.1" - password: "admin" - rule_name: "Web SSH" - from_zone: ["external"] - to_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" - commit: False -''' - -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 - -_NAT_XPATH = "/config/devices/entry[@name='localhost.localdomain']" + \ - "/vsys/entry[@name='vsys1']" + \ - "/rulebase/nat/rules/entry[@name='%s']" - - -def nat_rule_exists(xapi, rule_name): - xapi.get(_NAT_XPATH % rule_name) - e = xapi.element_root.find('.//entry') - if e is None: - return False - return True - - -def dnat_xml(m, dnat_address, dnat_port): - if dnat_address is None and dnat_port is None: - return None - - exml = [""] - if dnat_address is not None: - exml.append("%s" % - dnat_address) - if dnat_port is not None: - exml.append("%s" % - dnat_port) - exml.append('') - - return ''.join(exml) - - -def snat_xml(m, snat_type, snat_address, snat_interface, - snat_interface_address, snat_bidirectional): - if snat_type == 'static-ip': - if snat_address is None: - m.fail_json(msg="snat_address should be speicified " - "for snat_type static-ip") - - exml = ["", ""] - if snat_bidirectional: - exml.append('%s' % 'yes') - else: - exml.append('%s' % 'no') - exml.append('%s' % - snat_address) - exml.append('') - exml.append('') - elif snat_type == 'dynamic-ip-and-port': - exml = ["", - ""] - if snat_interface is not None: - exml = exml + [ - "", - "%s" % snat_interface] - if snat_interface_address is not None: - exml.append("%s" % snat_interface_address) - exml.append("") - elif snat_address is not None: - exml.append("") - for t in snat_address: - exml.append("%s" % t) - exml.append("") - else: - m.fail_json(msg="no snat_interface or snat_address " - "specified for snat_type dynamic-ip-and-port") - exml.append('') - exml.append('') - else: - m.fail_json(msg="unknown snat_type %s" % snat_type) - - return ''.join(exml) - - -def add_nat(xapi, module, rule_name, from_zone, to_zone, - source, destination, service, dnatxml=None, snatxml=None): - exml = [] - if dnatxml: - exml.append(dnatxml) - if snatxml: - exml.append(snatxml) - - exml.append("%s" % to_zone) - - exml.append("") - exml = exml + ["%s" % e for e in from_zone] - exml.append("") - - exml.append("") - exml = exml + ["%s" % e for e in source] - exml.append("") - - exml.append("") - exml = exml + ["%s" % e for e in destination] - exml.append("") - - exml.append("%s" % service) - - exml.append("ipv4") - - exml = ''.join(exml) - - xapi.set(xpath=_NAT_XPATH % rule_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'), - rule_name=dict(required=True), - from_zone=dict(type='list', required=True), - to_zone=dict(required=True), - source=dict(type='list', default=["any"]), - destination=dict(type='list', default=["any"]), - service=dict(default="any"), - snat_type=dict(), - snat_address=dict(), - snat_interface=dict(), - snat_interface_address=dict(), - snat_bidirectional=dict(default=False), - dnat_address=dict(), - dnat_port=dict(), - override=dict(type='bool', default=False), - commit=dict(type='bool', default=True) - ) - module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) - - if module._name == 'panos_nat_policy': - module.deprecate("The 'panos_nat_policy' module is being renamed 'panos_nat_rule'", version=2.9) - - 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 - ) - - rule_name = module.params['rule_name'] - from_zone = module.params['from_zone'] - to_zone = module.params['to_zone'] - source = module.params['source'] - destination = module.params['destination'] - service = module.params['service'] - - snat_type = module.params['snat_type'] - snat_address = module.params['snat_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'] - commit = module.params['commit'] - - override = module.params["override"] - if not override and nat_rule_exists(xapi, rule_name): - module.exit_json(changed=False, msg="rule exists") - - try: - changed = add_nat( - xapi, - module, - rule_name, - from_zone, - to_zone, - source, - destination, - service, - dnatxml=dnat_xml(module, dnat_address, dnat_port), - snatxml=snat_xml(module, snat_type, snat_address, - snat_interface, snat_interface_address, - snat_bidirectional) - ) - - if changed and commit: - xapi.commit(cmd="", sync=True, interval=1) - - module.exit_json(changed=changed, msg="okey dokey") - except PanXapiError as exc: - module.fail_json(msg=to_native(exc)) +from ansible.module_utils.common.removed import removed_module if __name__ == '__main__': - main() + removed_module(removed_in='2.9') diff --git a/lib/ansible/modules/network/panos/_panos_security_policy.py b/lib/ansible/modules/network/panos/_panos_security_policy.py index bf6718be404..45c6615b96d 100644 --- a/lib/ansible/modules/network/panos/_panos_security_policy.py +++ b/lib/ansible/modules/network/panos/_panos_security_policy.py @@ -3,507 +3,15 @@ # # 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 . +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['deprecated'], + 'status': ['removed'], 'supported_by': 'community'} -DOCUMENTATION = ''' ---- -module: panos_security_policy -short_description: Create security rule policy on PanOS 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: "Ivan Bojer (@ivanbojer)" -version_added: "2.3" -deprecated: - alternative: Use M(panos_security_rule) instead. - removed_in: '2.9' - why: This module depended on outdated and old SDK. In 2.4 use M(panos_security_rule) instead. -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/) -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. - 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: - description: - - Administrative tags that can be added to the rule. Note, tags must be already defined. - from_zone: - description: - - List of source zones. - default: "any" - to_zone: - description: - - List of destination zones. - default: "any" - source: - 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: - 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. - log_end: - description: - - Whether to log at session end. - default: true - 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. - default: true -''' - -EXAMPLES = ''' -- name: permit ssh to 1.1.1.1 - panos_security_policy: - ip_address: '10.5.172.91' - username: 'admin' - password: 'paloalto' - rule_name: 'SSH permit' - description: 'SSH rule test' - from_zone: ['public'] - to_zone: ['private'] - source: ['any'] - source_user: ['any'] - destination: ['1.1.1.1'] - category: ['any'] - application: ['ssh'] - service: ['application-default'] - hip_profiles: ['any'] - action: 'allow' - commit: false - -- name: Allow HTTP multimedia only from CDNs - panos_security_policy: - ip_address: '10.5.172.91' - username: 'admin' - password: 'paloalto' - rule_name: 'HTTP Multimedia' - description: 'Allow HTTP multimedia only to host at 1.1.1.1' - from_zone: ['public'] - to_zone: ['private'] - source: ['any'] - source_user: ['any'] - destination: ['1.1.1.1'] - category: ['content-delivery-networks'] - application: ['http-video', 'http-audio'] - service: ['service-http', 'service-https'] - hip_profiles: ['any'] - action: 'allow' - commit: false - -- name: more complex fictitious rule that uses profiles - panos_security_policy: - ip_address: '10.5.172.91' - username: 'admin' - password: 'paloalto' - 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' - commit: false - -- name: deny all - panos_security_policy: - ip_address: '10.5.172.91' - username: 'admin' - password: 'paloalto' - rule_name: 'DenyAll' - log_start: true - log_end: true - action: 'deny' - rule_type: 'interzone' - commit: false - -# permit ssh to 1.1.1.1 using panorama and pushing the configuration to firewalls -# that are defined in 'DeviceGroupA' device group -- name: permit ssh to 1.1.1.1 through Panorama - panos_security_policy: - ip_address: '10.5.172.92' - password: 'paloalto' - rule_name: 'SSH permit' - description: 'SSH rule test' - from_zone: ['public'] - to_zone: ['private'] - source: ['any'] - source_user: ['any'] - destination: ['1.1.1.1'] - category: ['any'] - application: ['ssh'] - service: ['application-default'] - hip_profiles: ['any'] - action: 'allow' - devicegroup: 'DeviceGroupA' -''' - -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 - import pandevice.firewall - import pandevice.panorama - import pandevice.objects - import pandevice.policies - - HAS_LIB = True -except ImportError: - HAS_LIB = False - - -def security_rule_exists(device, sec_rule): - if isinstance(device, pandevice.firewall.Firewall): - rule_base = pandevice.policies.Rulebase.refreshall(device) - elif isinstance(device, pandevice.panorama.Panorama): - # look for only pre-rulebase ATM - rule_base = pandevice.policies.PreRulebase.refreshall(device) - - 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'] - - change_check = False - if rule_base: - rule_base = rule_base[0] - security_rules = rule_base.findall(pandevice.policies.SecurityRule) - if security_rules: - for r in security_rules: - if r.name == sec_rule.name: - change_check = True - for check in match_check: - propose_check = getattr(sec_rule, check, None) - current_check = getattr(r, check, None) - if propose_check != current_check: - return True - for check in list_check: - propose_check = getattr(sec_rule, check, []) - current_check = getattr(r, check, []) - if set(propose_check) != set(current_check): - return True - if change_check: - return 'no_change' - return False - - -def create_security_rule(**kwargs): - security_rule = pandevice.policies.SecurityRule( - name=kwargs['rule_name'], - description=kwargs['description'], - tozone=kwargs['to_zone'], - fromzone=kwargs['from_zone'], - source=kwargs['source'], - source_user=kwargs['source_user'], - destination=kwargs['destination'], - category=kwargs['category'], - application=kwargs['application'], - service=kwargs['service'], - hip_profiles=kwargs['hip_profiles'], - log_start=kwargs['log_start'], - log_end=kwargs['log_end'], - type=kwargs['rule_type'], - action=kwargs['action']) - - if 'tag' in kwargs: - security_rule.tag = kwargs['tag'] - - # 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_security_rule(device, sec_rule, rule_exist): - if isinstance(device, pandevice.firewall.Firewall): - rule_base = pandevice.policies.Rulebase.refreshall(device) - elif isinstance(device, pandevice.panorama.Panorama): - # look for only pre-rulebase ATM - rule_base = pandevice.policies.PreRulebase.refreshall(device) - - if rule_exist: - return False - if rule_base: - rule_base = rule_base[0] - - rule_base.add(sec_rule) - sec_rule.create() - - return True - else: - return False - - -def _commit(device, device_group=None): - """ - :param device: either firewall or panorama - :param device_group: panorama device group or if none then 'all' - :return: True if successful - """ - result = device.commit(sync=True) - - if isinstance(device, pandevice.panorama.Panorama): - result = device.commit_all(sync=True, sync_all=True, devicegroup=device_group) - - return result - - -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), - rule_name=dict(required=True), - description=dict(default=''), - tag=dict(), - to_zone=dict(type='list', default=['any']), - from_zone=dict(type='list', default=['any']), - source=dict(type='list', default=["any"]), - source_user=dict(type='list', default=['any']), - destination=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 module._name == 'panos_security_policy': - module.deprecate("The 'panos_security_policy' module is being renamed 'panos_security_rule'", version=2.9) - - if not HAS_LIB: - module.fail_json(msg='Missing required pan-python and pandevice modules.') - - ip_address = module.params["ip_address"] - password = module.params["password"] - username = module.params['username'] - api_key = module.params['api_key'] - rule_name = module.params['rule_name'] - description = module.params['description'] - tag = module.params['tag'] - from_zone = module.params['from_zone'] - to_zone = module.params['to_zone'] - source = module.params['source'] - source_user = module.params['source_user'] - destination = module.params['destination'] - category = module.params['category'] - application = module.params['application'] - service = module.params['service'] - hip_profiles = module.params['hip_profiles'] - log_start = module.params['log_start'] - log_end = module.params['log_end'] - rule_type = module.params['rule_type'] - 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'] - - devicegroup = module.params['devicegroup'] - - commit = module.params['commit'] - - if devicegroup: - device = pandevice.panorama.Panorama(ip_address, username, password, api_key=api_key) - dev_grps = device.refresh_devices() - - for grp in dev_grps: - if grp.name == devicegroup: - break - module.fail_json(msg=' \'%s\' device group not found in Panorama. Is the name correct?' % devicegroup) - else: - device = pandevice.firewall.Firewall(ip_address, username, password, api_key=api_key) - - sec_rule = create_security_rule( - rule_name=rule_name, - description=description, - tag=tag, - from_zone=from_zone, - to_zone=to_zone, - source=source, - source_user=source_user, - destination=destination, - 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 - ) - - rule_exist = security_rule_exists(device, sec_rule) - if rule_exist is True: - module.fail_json(msg='Rule with the same name but different objects exists.') - try: - changed = add_security_rule(device, sec_rule, rule_exist) - except PanXapiError as exc: - module.fail_json(msg=to_native(exc)) - - if changed and commit: - result = _commit(device, devicegroup) - - module.exit_json(changed=changed, msg="okey dokey") +from ansible.module_utils.common.removed import removed_module if __name__ == '__main__': - main() + removed_module(removed_in='2.9') diff --git a/test/sanity/ansible-doc/skip.txt b/test/sanity/ansible-doc/skip.txt index 1c1e7e7fb5f..99a8e526e7c 100644 --- a/test/sanity/ansible-doc/skip.txt +++ b/test/sanity/ansible-doc/skip.txt @@ -1,5 +1,18 @@ async_wrapper accelerate +aos_asn_pool +aos_blueprint +aos_blueprint_param +aos_blueprint_virtnet +aos_device +aos_external_router +aos_ip_pool +aos_logical_device +aos_logical_device_map +aos_login +aos_rack_type +aos_template +azure cl_bond cl_bridge cl_img_install @@ -7,15 +20,23 @@ cl_interface cl_interface_policy cl_license cl_ports +cs_nic docker +ec2_ami_find ec2_ami_search ec2_facts +ec2_remote_facts ec2_vpc +kubernetes +netscaler +nxos_ip_interface nxos_mtu +nxos_portchannel +nxos_switchport +oc +os_server_actions +panos_nat_policy +panos_security_policy s3 -azure -cs_nic -ec2_remote_facts -netscaler +vsphere_guest win_msi -os_server_actions diff --git a/test/sanity/validate-modules/ignore.txt b/test/sanity/validate-modules/ignore.txt index f596aab1746..13794824ee9 100644 --- a/test/sanity/validate-modules/ignore.txt +++ b/test/sanity/validate-modules/ignore.txt @@ -1,5 +1,3 @@ -lib/ansible/modules/cloud/amazon/_ec2_ami_find.py E322 -lib/ansible/modules/cloud/amazon/_ec2_ami_find.py E323 lib/ansible/modules/cloud/amazon/aws_api_gateway.py E322 lib/ansible/modules/cloud/amazon/aws_application_scaling_policy.py E322 lib/ansible/modules/cloud/amazon/aws_application_scaling_policy.py E326 @@ -315,9 +313,6 @@ lib/ansible/modules/clustering/consul_kv.py E322 lib/ansible/modules/clustering/consul_kv.py E324 lib/ansible/modules/clustering/consul_session.py E322 lib/ansible/modules/clustering/etcd3.py E326 -lib/ansible/modules/clustering/k8s/_kubernetes.py E322 -lib/ansible/modules/clustering/k8s/_kubernetes.py E323 -lib/ansible/modules/clustering/k8s/_kubernetes.py E324 lib/ansible/modules/clustering/znode.py E326 lib/ansible/modules/commands/command.py E322 lib/ansible/modules/commands/command.py E323 @@ -583,7 +578,6 @@ lib/ansible/modules/network/netvisor/pn_vrouterbgp.py E324 lib/ansible/modules/network/netvisor/pn_vrouterif.py E324 lib/ansible/modules/network/netvisor/pn_vrouterif.py E326 lib/ansible/modules/network/netvisor/pn_vrouterlbif.py E324 -lib/ansible/modules/network/nxos/_nxos_portchannel.py E324 lib/ansible/modules/network/nxos/nxos_aaa_server.py E326 lib/ansible/modules/network/nxos/nxos_acl.py E326 lib/ansible/modules/network/nxos/nxos_bgp.py E324 @@ -614,10 +608,6 @@ lib/ansible/modules/network/ordnance/ordnance_config.py E324 lib/ansible/modules/network/ordnance/ordnance_facts.py E322 lib/ansible/modules/network/ordnance/ordnance_facts.py E324 lib/ansible/modules/network/ovs/openvswitch_bridge.py E326 -lib/ansible/modules/network/panos/_panos_nat_policy.py E324 -lib/ansible/modules/network/panos/_panos_nat_policy.py E335 -lib/ansible/modules/network/panos/_panos_security_policy.py E322 -lib/ansible/modules/network/panos/_panos_security_policy.py E324 lib/ansible/modules/network/panos/panos_check.py E324 lib/ansible/modules/network/panos/panos_match_rule.py E324 lib/ansible/modules/network/panos/panos_match_rule.py E326 diff --git a/test/units/modules/network/nxos/test_nxos_ip_interface.py b/test/units/modules/network/nxos/test_nxos_ip_interface.py deleted file mode 100644 index 2585b6a43be..00000000000 --- a/test/units/modules/network/nxos/test_nxos_ip_interface.py +++ /dev/null @@ -1,79 +0,0 @@ -# (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 . - -# Make coding more python3-ish -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -from units.compat.mock import patch -from ansible.modules.network.nxos import _nxos_ip_interface -from .nxos_module import TestNxosModule, load_fixture, set_module_args - - -class TestNxosIPInterfaceModule(TestNxosModule): - - module = _nxos_ip_interface - - def setUp(self): - super(TestNxosIPInterfaceModule, self).setUp() - - self.mock_get_interface_mode = patch( - 'ansible.modules.network.nxos._nxos_ip_interface.get_interface_mode') - self.get_interface_mode = self.mock_get_interface_mode.start() - - self.mock_send_show_command = patch( - 'ansible.modules.network.nxos._nxos_ip_interface.send_show_command') - self.send_show_command = self.mock_send_show_command.start() - - self.mock_load_config = patch('ansible.modules.network.nxos._nxos_ip_interface.load_config') - self.load_config = self.mock_load_config.start() - - self.mock_get_capabilities = patch('ansible.modules.network.nxos._nxos_ip_interface.get_capabilities') - self.get_capabilities = self.mock_get_capabilities.start() - self.get_capabilities.return_value = {'network_api': 'cliconf'} - - def tearDown(self): - super(TestNxosIPInterfaceModule, self).tearDown() - self.mock_get_interface_mode.stop() - self.mock_send_show_command.stop() - self.mock_load_config.stop() - self.mock_get_capabilities.stop() - - def load_fixtures(self, commands=None, device=''): - self.get_interface_mode.return_value = 'layer3' - self.send_show_command.return_value = [load_fixture('', '_nxos_ip_interface.cfg')] - self.load_config.return_value = None - - def test_nxos_ip_interface_ip_present(self): - set_module_args(dict(interface='eth2/1', addr='1.1.1.2', mask=8)) - result = self.execute_module(changed=True) - self.assertEqual(result['commands'], - ['interface eth2/1', - 'no ip address 192.0.2.1/8', - 'ip address 1.1.1.2/8']) - - def test_nxos_ip_interface_ip_idempotent(self): - set_module_args(dict(interface='eth2/1', addr='192.0.2.1', mask=8)) - result = self.execute_module(changed=False) - self.assertEqual(result['commands'], []) - - def test_nxos_ip_interface_ip_absent(self): - set_module_args(dict(interface='eth2/1', state='absent', - addr='192.0.2.1', mask=8)) - result = self.execute_module(changed=True) - self.assertEqual(result['commands'], - ['interface eth2/1', 'no ip address 192.0.2.1/8']) diff --git a/test/units/modules/network/nxos/test_nxos_portchannel.py b/test/units/modules/network/nxos/test_nxos_portchannel.py deleted file mode 100644 index df23856f62c..00000000000 --- a/test/units/modules/network/nxos/test_nxos_portchannel.py +++ /dev/null @@ -1,67 +0,0 @@ -# (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 . - -# Make coding more python3-ish -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -from units.compat.mock import patch -from ansible.modules.network.nxos import _nxos_portchannel -from .nxos_module import TestNxosModule, set_module_args - - -class TestNxosPortchannelModule(TestNxosModule): - - module = _nxos_portchannel - - def setUp(self): - super(TestNxosPortchannelModule, self).setUp() - - self.mock_run_commands = patch('ansible.modules.network.nxos._nxos_portchannel.run_commands') - self.run_commands = self.mock_run_commands.start() - - self.mock_load_config = patch('ansible.modules.network.nxos._nxos_portchannel.load_config') - self.load_config = self.mock_load_config.start() - - self.mock_get_config = patch('ansible.modules.network.nxos._nxos_portchannel.get_config') - self.get_config = self.mock_get_config.start() - - self.mock_get_capabilities = patch('ansible.modules.network.nxos._nxos_portchannel.get_capabilities') - self.get_capabilities = self.mock_get_capabilities.start() - self.get_capabilities.return_value = {'network_api': 'cliconf'} - - def tearDown(self): - super(TestNxosPortchannelModule, self).tearDown() - self.mock_run_commands.stop() - self.mock_load_config.stop() - self.mock_get_config.stop() - self.mock_get_capabilities.stop() - - def load_fixtures(self, commands=None, device=''): - self.load_config.return_value = None - - def test_nxos_portchannel(self): - set_module_args(dict(group='99', - members=['Ethernet2/1', 'Ethernet2/2'], - mode='active', - state='present')) - result = self.execute_module(changed=True) - self.assertEqual(result['commands'], ['interface port-channel99', - 'interface Ethernet2/1', - 'channel-group 99 mode active', - 'interface Ethernet2/2', - 'channel-group 99 mode active']) diff --git a/test/units/modules/network/nxos/test_nxos_switchport.py b/test/units/modules/network/nxos/test_nxos_switchport.py deleted file mode 100644 index 89463f41d19..00000000000 --- a/test/units/modules/network/nxos/test_nxos_switchport.py +++ /dev/null @@ -1,80 +0,0 @@ -# (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 . - -# Make coding more python3-ish -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -from units.compat.mock import patch -from ansible.modules.network.nxos import _nxos_switchport -from .nxos_module import TestNxosModule, load_fixture, set_module_args - - -class TestNxosSwitchportModule(TestNxosModule): - - module = _nxos_switchport - - def setUp(self): - super(TestNxosSwitchportModule, self).setUp() - - self.mock_run_commands = patch('ansible.modules.network.nxos._nxos_switchport.run_commands') - self.run_commands = self.mock_run_commands.start() - - self.mock_load_config = patch('ansible.modules.network.nxos._nxos_switchport.load_config') - self.load_config = self.mock_load_config.start() - - self.mock_get_capabilities = patch('ansible.modules.network.nxos._nxos_switchport.get_capabilities') - self.get_capabilities = self.mock_get_capabilities.start() - self.get_capabilities.return_value = {'network_api': 'cliconf'} - - def tearDown(self): - super(TestNxosSwitchportModule, self).tearDown() - self.mock_run_commands.stop() - self.mock_load_config.stop() - self.mock_get_capabilities.stop() - - def load_fixtures(self, commands=None, device=''): - def load_from_file(*args, **kwargs): - module, commands = args - output = list() - for command in commands: - filename = str(command).split(' | ')[0].replace(' ', '_') - filename = filename.replace('2/1', '') - output.append(load_fixture('_nxos_switchport', filename)) - return output - - self.run_commands.side_effect = load_from_file - self.load_config.return_value = None - - def test_nxos_switchport_present(self): - set_module_args(dict(interface='Ethernet2/1', mode='access', access_vlan=1, state='present')) - result = self.execute_module(changed=True) - self.assertEqual(result['commands'], ['interface ethernet2/1', 'switchport access vlan 1']) - - def test_nxos_switchport_unconfigured(self): - set_module_args(dict(interface='Ethernet2/1', state='unconfigured')) - result = self.execute_module(changed=True) - self.assertEqual(result['commands'], ['interface ethernet2/1', - 'switchport mode access', - 'switch access vlan 1', - 'switchport trunk native vlan 1', - 'switchport trunk allowed vlan all']) - - def test_nxos_switchport_absent(self): - set_module_args(dict(interface='Ethernet2/1', mode='access', access_vlan=3, state='absent')) - result = self.execute_module(changed=False) - self.assertEqual(result['commands'], [])