plugins/modules/ali_eni.py (337 lines of code) (raw):

#!/usr/bin/python # Copyright (c) 2017-present Alibaba Group Holding Limited. <xiaozhu36> # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import (absolute_import, division, print_function) __metaclass__ = type ANSIBLE_METADATA = {'metadata_version': '1.1', 'status': ['preview'], 'supported_by': 'community'} DOCUMENTATION = ''' --- module: ali_eni short_description: Create and optionally attach an Elastic Network Interface (ENI) to an instance description: - Create and optionally attach an Elastic Network Interface (ENI) to an instance. If an ENI ID or private ip with vswitch id is provided, the existing ENI (if any) will be modified. options: state: description: - Create or delete ENI. default: 'present' choices: ['present', 'absent'] type: str eni_id: description: - The ID of the ENI (to modify); if null and state is present, a new eni will be created. aliases: ['id'] type: str instance_id: description: - Instance ID that you wish to attach ENI to. type: str private_ip_address: description: - Private IP address. If null and state is present, a vacant IP address will be allocated. aliases: ['private_ip'] type: str vswitch_id: description: - ID of subnet in which to create the ENI. aliases: ['subnet_id'] type: str name: description: - Optional name of the ENI. It is a string of [2, 128] Chinese or English characters. It must begin with a letter and can contain numbers, underscores ("_"), colons (":"), or hyphens ("-"). It cannot begin with http:// or https://. type: str description: description: - Optional description of the ENI. type: str security_groups: description: - List of security group ids associated with the interface. type: list elements: str attached: description: - Specifies if network interface should be attached or detached from instance. If ommited, attachment status won't change type: bool default: False tags: description: - A hash/dictionaries of network interface tags. C({"key":"value"}) type: dict purge_tags: description: - Delete existing tags on the network interface that are not specified in the task. If True, it means you have to specify all the desired tags on each task affecting a network interface. default: False type: bool author: - "He Guimin (@xiaozhu36)" requirements: - "python >= 3.6" - "footmark >= 1.13.0" extends_documentation_fragment: - alibaba.alicloud.alicloud notes: - This module identifies and ENI based on either the eni_id or a combination of private_ip_address and vswitch_id. Any of these options will let you specify a particular ENI. - Only name, description and security_groups can be updated. ''' EXAMPLES = ''' # Note: These examples do not set authentication details, see the Alibaba Cloud Guide for details. - name: Create an ENI alibaba.alicloud.ali_eni: private_ip_address: 172.31.0.20 vswitch_id: vsw-xxxxxxxx security_groups: - sg-xxxxxx1 - sg-xxxxxx2 - name: Create an ENI and attach it to an instance alibaba.alicloud.ali_eni: instance_id: i-xxxxxxx private_ip_address: 172.31.0.20 vswitch_id: vsw-xxxxxxxx security_groups: - sg-xxxxxx1 - sg-xxxxxx2 attached: True - name: Update an ENI alibaba.alicloud.ali_eni: eni_id: eni-xxxxxxx name: my-eni description: "My new description" - name: Update an ENI identifying it by private_ip_address and subnet_id alibaba.alicloud.ali_eni: vswitch_id: vsw-xxxxxxx private_ip_address: 172.16.1.1 description: "My new description" security_groups: - sg-xxxxxx3 - sg-xxxxxx4 - name: Detach an ENI from an instance alibaba.alicloud.ali_eni: eni_id: eni-xxxxxxx instance_id: i-xxxxxx attached: False state: present - name: Delete an interface alibaba.alicloud.ali_eni: eni_id: "{{ eni.interface.id }}" state: absent - name: First create the interface alibaba.alicloud.ali_eni: vswitch_id: vsw-xxxxxxxx security_groups: - sg-xxxxxx1 - sg-xxxxxx2 state: present register: eni ''' RETURN = ''' interface: description: info about the elastic network interfaces that was created or deleted. returned: always type: complex contains: associated_public_ip: description: The public IP address associated with the ENI. type: str sample: 42.1.10.1 zone_id: description: The availability zone of the ENI is in. returned: always type: str sample: cn-beijing-a name: description: interface name type: str sample: my-eni creation_time: description: The time the eni was created. returned: always type: str sample: "2018-06-25T04:08Z" description: description: interface description type: str sample: My new network interface security_groups: description: list of security group ids type: list sample: ["sg-f8a8a9da", "sg-xxxxxx"] network_interface_id: description: network interface id type: str sample: "eni-123456" id: description: network interface id (alias for network_interface_id) type: str sample: "eni-123456" instance_id: description: Attached instance id type: str sample: "i-123456" mac_address: description: interface's physical address type: str sample: "00:00:5E:00:53:23" private_ip_address: description: primary ip address of this interface type: str sample: 10.20.30.40 private_ip_addresses: description: list of all private ip addresses associated to this interface type: list sample: [{"primary_address": true, "private_ip_address": "10.20.30.40"}] state: description: network interface status type: str sample: "pending" vswitch_id: description: which vswitch the interface is bound type: str sample: vsw-b33i43f3 vpc_id: description: which vpc this network interface is bound type: str sample: vpc-cj3ht4ogn type: description: type of the ENI type: str sample: Secondary tags: description: Any tags assigned to the ENI. returned: always type: dict sample: {} ''' import time from ansible.module_utils.basic import AnsibleModule from ansible_collections.alibaba.alicloud.plugins.module_utils.alicloud_ecs import ecs_argument_spec, ecs_connect HAS_FOOTMARK = False try: from footmark.exception import ECSResponseError HAS_FOOTMARK = True except ImportError: HAS_FOOTMARK = False def uniquely_find_eni(connection, module): eni_id = module.params.get("eni_id") private_ip_address = module.params.get('private_ip_address') vswitch_id = module.params.get('vswitch_id') try: if not eni_id and not private_ip_address and not vswitch_id: return None params = {'network_interface_ids': [eni_id], 'primary_ip_address': private_ip_address, 'vswitch_id': vswitch_id } enis = connection.describe_network_interfaces(**params) if enis and len(enis) == 1: return enis[0] return None except Exception as e: module.fail_json(msg="{0}".format(e)) def main(): argument_spec = ecs_argument_spec() argument_spec.update( dict( eni_id=dict(type='str', aliases=['id']), instance_id=dict(type='str'), private_ip_address=dict(type='str', aliases=['private_ip']), vswitch_id=dict(type='str', aliases=['subnet_id']), description=dict(type='str'), name=dict(type='str'), security_groups=dict(type='list', elements='str'), state=dict(default='present', choices=['present', 'absent']), attached=dict(default=False, type='bool'), tags=dict(type='dict'), purge_tags=dict(type='bool', default=False) ) ) module = AnsibleModule(argument_spec=argument_spec, required_if=([ ('attached', True, ['instance_id']) ]) ) if not HAS_FOOTMARK: module.fail_json(msg='footmark required for this module') ecs = ecs_connect(module) state = module.params.get("state") eni = uniquely_find_eni(ecs, module) changed = False if state == 'absent': if not eni: module.exit_json(changed=changed, interface={}) try: changed = eni.delete() module.exit_json(changed=changed, interface={}) except Exception as e: module.fail_json(msg="{0}".format(e)) # when state is present group_ids = module.params.get("security_groups") name = module.params.get("name") description = module.params.get("description") if not eni: if not group_ids: module.fail_json(msg="'security_groups' is required when creating a new ENI") vswitch_id = module.params.get("vswitch_id") if not vswitch_id: module.fail_json(msg="'vswitch_id' is required when creating a new ENI") try: params = module.params params['security_group_id'] = group_ids[0] params['primary_ip_address'] = module.params.get("private_ip_address") params['network_interface_name'] = module.params.get("name") params['client_token'] = "Ansible-Alicloud-{0}-{1}".format(hash(str(module.params)), str(time.time())) eni = ecs.create_network_interface(**params) module.exit_json(changed=True, interface=eni.get().read()) except Exception as e: module.fail_json(msg="{0}".format(e)) if not group_ids: group_ids = eni.security_group_ids["security_group_id"] if not name: name = eni.name if not description: description = eni.description try: if eni.modify(group_ids, name, description): changed = True except Exception as e: module.fail_json(msg="{0}".format(e)) attached = module.params.get("attached") instance_id = module.params.get("instance_id") try: if attached: if not eni.instance_id: if eni.attach(instance_id): changed = True elif eni.instance_id != instance_id and eni.detach(eni.instance_id) and eni.attach(instance_id): changed = True else: if eni.detach(eni.instance_id): changed = True except Exception as e: module.fail_json(msg="{0}".format(e)) tags = module.params['tags'] if module.params['purge_tags']: removed = {} if not tags: removed = eni.tags else: for key, value in list(eni.tags.items()): if key not in list(tags.keys()): removed[key] = value try: if eni.remove_tags(removed): changed = True module.exit_json(changed=changed, interface=eni.get().read()) except Exception as e: module.fail_json(msg="{0}".format(e)) if tags: try: if eni.add_tags(tags): changed = True except Exception as e: module.fail_json(msg="{0}".format(e)) module.exit_json(changed=changed, interface=eni.get().read()) if __name__ == '__main__': main()