mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-04-27 04:41:26 -07:00
Unflatmap community.general (#5461)
* Move files. * Update imports and references. * Move wrongly placed files. * Reverse redirects, deprecate long → short name redirects. * Simplify contribution guidelines for new modules. * Rewrite BOTMETA. * Add changelog fragment. * Fix ignore.txt files.
This commit is contained in:
parent
2b0bebc8fc
commit
b531ecdc9b
1033 changed files with 4802 additions and 1989 deletions
558
plugins/modules/dimensiondata_vlan.py
Normal file
558
plugins/modules/dimensiondata_vlan.py
Normal file
|
@ -0,0 +1,558 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2016 Dimension Data
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# Authors:
|
||||
# - Adam Friedman <tintoy@tintoy.io>
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: dimensiondata_vlan
|
||||
short_description: Manage a VLAN in a Cloud Control network domain.
|
||||
extends_documentation_fragment:
|
||||
- community.general.dimensiondata
|
||||
- community.general.dimensiondata_wait
|
||||
|
||||
description:
|
||||
- Manage VLANs in Cloud Control network domains.
|
||||
author: 'Adam Friedman (@tintoy)'
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- The name of the target VLAN.
|
||||
type: str
|
||||
required: true
|
||||
description:
|
||||
description:
|
||||
- A description of the VLAN.
|
||||
type: str
|
||||
default: ''
|
||||
network_domain:
|
||||
description:
|
||||
- The Id or name of the target network domain.
|
||||
required: true
|
||||
type: str
|
||||
private_ipv4_base_address:
|
||||
description:
|
||||
- The base address for the VLAN's IPv4 network (e.g. 192.168.1.0).
|
||||
type: str
|
||||
default: ''
|
||||
private_ipv4_prefix_size:
|
||||
description:
|
||||
- The size of the IPv4 address space, e.g 24.
|
||||
- Required, if C(private_ipv4_base_address) is specified.
|
||||
type: int
|
||||
default: 0
|
||||
state:
|
||||
description:
|
||||
- The desired state for the target VLAN.
|
||||
- C(readonly) ensures that the state is only ever read, not modified (the module will fail if the resource does not exist).
|
||||
choices: [present, absent, readonly]
|
||||
default: present
|
||||
type: str
|
||||
allow_expand:
|
||||
description:
|
||||
- Permit expansion of the target VLAN's network if the module parameters specify a larger network than the VLAN currently possesses.
|
||||
- If C(False), the module will fail under these conditions.
|
||||
- This is intended to prevent accidental expansion of a VLAN's network (since this operation is not reversible).
|
||||
type: bool
|
||||
default: false
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Add or update VLAN
|
||||
community.general.dimensiondata_vlan:
|
||||
region: na
|
||||
location: NA5
|
||||
network_domain: test_network
|
||||
name: my_vlan1
|
||||
description: A test VLAN
|
||||
private_ipv4_base_address: 192.168.23.0
|
||||
private_ipv4_prefix_size: 24
|
||||
state: present
|
||||
wait: true
|
||||
|
||||
- name: Read / get VLAN details
|
||||
community.general.dimensiondata_vlan:
|
||||
region: na
|
||||
location: NA5
|
||||
network_domain: test_network
|
||||
name: my_vlan1
|
||||
state: readonly
|
||||
wait: true
|
||||
|
||||
- name: Delete a VLAN
|
||||
community.general.dimensiondata_vlan:
|
||||
region: na
|
||||
location: NA5
|
||||
network_domain: test_network
|
||||
name: my_vlan_1
|
||||
state: absent
|
||||
wait: true
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
vlan:
|
||||
description: Dictionary describing the VLAN.
|
||||
returned: On success when I(state) is 'present'
|
||||
type: complex
|
||||
contains:
|
||||
id:
|
||||
description: VLAN ID.
|
||||
type: str
|
||||
sample: "aaaaa000-a000-4050-a215-2808934ccccc"
|
||||
name:
|
||||
description: VLAN name.
|
||||
type: str
|
||||
sample: "My VLAN"
|
||||
description:
|
||||
description: VLAN description.
|
||||
type: str
|
||||
sample: "My VLAN description"
|
||||
location:
|
||||
description: Datacenter location.
|
||||
type: str
|
||||
sample: NA3
|
||||
private_ipv4_base_address:
|
||||
description: The base address for the VLAN's private IPV4 network.
|
||||
type: str
|
||||
sample: 192.168.23.0
|
||||
private_ipv4_prefix_size:
|
||||
description: The prefix size for the VLAN's private IPV4 network.
|
||||
type: int
|
||||
sample: 24
|
||||
private_ipv4_gateway_address:
|
||||
description: The gateway address for the VLAN's private IPV4 network.
|
||||
type: str
|
||||
sample: 192.168.23.1
|
||||
private_ipv6_base_address:
|
||||
description: The base address for the VLAN's IPV6 network.
|
||||
type: str
|
||||
sample: 2402:9900:111:1195:0:0:0:0
|
||||
private_ipv6_prefix_size:
|
||||
description: The prefix size for the VLAN's IPV6 network.
|
||||
type: int
|
||||
sample: 64
|
||||
private_ipv6_gateway_address:
|
||||
description: The gateway address for the VLAN's IPV6 network.
|
||||
type: str
|
||||
sample: 2402:9900:111:1195:0:0:0:1
|
||||
status:
|
||||
description: VLAN status.
|
||||
type: str
|
||||
sample: NORMAL
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible_collections.community.general.plugins.module_utils.dimensiondata import DimensionDataModule, UnknownNetworkError
|
||||
|
||||
try:
|
||||
from libcloud.common.dimensiondata import DimensionDataVlan, DimensionDataAPIException
|
||||
|
||||
HAS_LIBCLOUD = True
|
||||
|
||||
except ImportError:
|
||||
DimensionDataVlan = None
|
||||
|
||||
HAS_LIBCLOUD = False
|
||||
|
||||
|
||||
class DimensionDataVlanModule(DimensionDataModule):
|
||||
"""
|
||||
The dimensiondata_vlan module for Ansible.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Create a new Dimension Data VLAN module.
|
||||
"""
|
||||
|
||||
super(DimensionDataVlanModule, self).__init__(
|
||||
module=AnsibleModule(
|
||||
argument_spec=DimensionDataModule.argument_spec_with_wait(
|
||||
name=dict(required=True, type='str'),
|
||||
description=dict(default='', type='str'),
|
||||
network_domain=dict(required=True, type='str'),
|
||||
private_ipv4_base_address=dict(default='', type='str'),
|
||||
private_ipv4_prefix_size=dict(default=0, type='int'),
|
||||
allow_expand=dict(required=False, default=False, type='bool'),
|
||||
state=dict(default='present', choices=['present', 'absent', 'readonly'])
|
||||
),
|
||||
required_together=DimensionDataModule.required_together()
|
||||
)
|
||||
)
|
||||
|
||||
self.name = self.module.params['name']
|
||||
self.description = self.module.params['description']
|
||||
self.network_domain_selector = self.module.params['network_domain']
|
||||
self.private_ipv4_base_address = self.module.params['private_ipv4_base_address']
|
||||
self.private_ipv4_prefix_size = self.module.params['private_ipv4_prefix_size']
|
||||
self.state = self.module.params['state']
|
||||
self.allow_expand = self.module.params['allow_expand']
|
||||
|
||||
if self.wait and self.state != 'present':
|
||||
self.module.fail_json(
|
||||
msg='The wait parameter is only supported when state is "present".'
|
||||
)
|
||||
|
||||
def state_present(self):
|
||||
"""
|
||||
Ensure that the target VLAN is present.
|
||||
"""
|
||||
|
||||
network_domain = self._get_network_domain()
|
||||
|
||||
vlan = self._get_vlan(network_domain)
|
||||
if not vlan:
|
||||
if self.module.check_mode:
|
||||
self.module.exit_json(
|
||||
msg='VLAN "{0}" is absent from network domain "{1}" (should be present).'.format(
|
||||
self.name, self.network_domain_selector
|
||||
),
|
||||
changed=True
|
||||
)
|
||||
|
||||
vlan = self._create_vlan(network_domain)
|
||||
self.module.exit_json(
|
||||
msg='Created VLAN "{0}" in network domain "{1}".'.format(
|
||||
self.name, self.network_domain_selector
|
||||
),
|
||||
vlan=vlan_to_dict(vlan),
|
||||
changed=True
|
||||
)
|
||||
else:
|
||||
diff = VlanDiff(vlan, self.module.params)
|
||||
if not diff.has_changes():
|
||||
self.module.exit_json(
|
||||
msg='VLAN "{0}" is present in network domain "{1}" (no changes detected).'.format(
|
||||
self.name, self.network_domain_selector
|
||||
),
|
||||
vlan=vlan_to_dict(vlan),
|
||||
changed=False
|
||||
)
|
||||
|
||||
return
|
||||
|
||||
try:
|
||||
diff.ensure_legal_change()
|
||||
except InvalidVlanChangeError as invalid_vlan_change:
|
||||
self.module.fail_json(
|
||||
msg='Unable to update VLAN "{0}" in network domain "{1}": {2}'.format(
|
||||
self.name, self.network_domain_selector, invalid_vlan_change
|
||||
)
|
||||
)
|
||||
|
||||
if diff.needs_expand() and not self.allow_expand:
|
||||
self.module.fail_json(
|
||||
msg='The configured private IPv4 network size ({0}-bit prefix) for '.format(
|
||||
self.private_ipv4_prefix_size
|
||||
) + 'the VLAN differs from its current network size ({0}-bit prefix) '.format(
|
||||
vlan.private_ipv4_range_size
|
||||
) + 'and needs to be expanded. Use allow_expand=true if this is what you want.'
|
||||
)
|
||||
|
||||
if self.module.check_mode:
|
||||
self.module.exit_json(
|
||||
msg='VLAN "{0}" is present in network domain "{1}" (changes detected).'.format(
|
||||
self.name, self.network_domain_selector
|
||||
),
|
||||
vlan=vlan_to_dict(vlan),
|
||||
changed=True
|
||||
)
|
||||
|
||||
if diff.needs_edit():
|
||||
vlan.name = self.name
|
||||
vlan.description = self.description
|
||||
|
||||
self.driver.ex_update_vlan(vlan)
|
||||
|
||||
if diff.needs_expand():
|
||||
vlan.private_ipv4_range_size = self.private_ipv4_prefix_size
|
||||
self.driver.ex_expand_vlan(vlan)
|
||||
|
||||
self.module.exit_json(
|
||||
msg='Updated VLAN "{0}" in network domain "{1}".'.format(
|
||||
self.name, self.network_domain_selector
|
||||
),
|
||||
vlan=vlan_to_dict(vlan),
|
||||
changed=True
|
||||
)
|
||||
|
||||
def state_readonly(self):
|
||||
"""
|
||||
Read the target VLAN's state.
|
||||
"""
|
||||
|
||||
network_domain = self._get_network_domain()
|
||||
|
||||
vlan = self._get_vlan(network_domain)
|
||||
if vlan:
|
||||
self.module.exit_json(
|
||||
vlan=vlan_to_dict(vlan),
|
||||
changed=False
|
||||
)
|
||||
else:
|
||||
self.module.fail_json(
|
||||
msg='VLAN "{0}" does not exist in network domain "{1}".'.format(
|
||||
self.name, self.network_domain_selector
|
||||
)
|
||||
)
|
||||
|
||||
def state_absent(self):
|
||||
"""
|
||||
Ensure that the target VLAN is not present.
|
||||
"""
|
||||
|
||||
network_domain = self._get_network_domain()
|
||||
|
||||
vlan = self._get_vlan(network_domain)
|
||||
if not vlan:
|
||||
self.module.exit_json(
|
||||
msg='VLAN "{0}" is absent from network domain "{1}".'.format(
|
||||
self.name, self.network_domain_selector
|
||||
),
|
||||
changed=False
|
||||
)
|
||||
|
||||
return
|
||||
|
||||
if self.module.check_mode:
|
||||
self.module.exit_json(
|
||||
msg='VLAN "{0}" is present in network domain "{1}" (should be absent).'.format(
|
||||
self.name, self.network_domain_selector
|
||||
),
|
||||
vlan=vlan_to_dict(vlan),
|
||||
changed=True
|
||||
)
|
||||
|
||||
self._delete_vlan(vlan)
|
||||
|
||||
self.module.exit_json(
|
||||
msg='Deleted VLAN "{0}" from network domain "{1}".'.format(
|
||||
self.name, self.network_domain_selector
|
||||
),
|
||||
changed=True
|
||||
)
|
||||
|
||||
def _get_vlan(self, network_domain):
|
||||
"""
|
||||
Retrieve the target VLAN details from CloudControl.
|
||||
|
||||
:param network_domain: The target network domain.
|
||||
:return: The VLAN, or None if the target VLAN was not found.
|
||||
:rtype: DimensionDataVlan
|
||||
"""
|
||||
|
||||
vlans = self.driver.ex_list_vlans(
|
||||
location=self.location,
|
||||
network_domain=network_domain
|
||||
)
|
||||
matching_vlans = [vlan for vlan in vlans if vlan.name == self.name]
|
||||
if matching_vlans:
|
||||
return matching_vlans[0]
|
||||
|
||||
return None
|
||||
|
||||
def _create_vlan(self, network_domain):
|
||||
vlan = self.driver.ex_create_vlan(
|
||||
network_domain,
|
||||
self.name,
|
||||
self.private_ipv4_base_address,
|
||||
self.description,
|
||||
self.private_ipv4_prefix_size
|
||||
)
|
||||
|
||||
if self.wait:
|
||||
vlan = self._wait_for_vlan_state(vlan.id, 'NORMAL')
|
||||
|
||||
return vlan
|
||||
|
||||
def _delete_vlan(self, vlan):
|
||||
try:
|
||||
self.driver.ex_delete_vlan(vlan)
|
||||
|
||||
# Not currently supported for deletes due to a bug in libcloud (module will error out if "wait" is specified when "state" is not "present").
|
||||
if self.wait:
|
||||
self._wait_for_vlan_state(vlan, 'NOT_FOUND')
|
||||
|
||||
except DimensionDataAPIException as api_exception:
|
||||
self.module.fail_json(
|
||||
msg='Failed to delete VLAN "{0}" due to unexpected error from the CloudControl API: {1}'.format(
|
||||
vlan.id, api_exception.msg
|
||||
)
|
||||
)
|
||||
|
||||
def _wait_for_vlan_state(self, vlan, state_to_wait_for):
|
||||
network_domain = self._get_network_domain()
|
||||
|
||||
wait_poll_interval = self.module.params['wait_poll_interval']
|
||||
wait_time = self.module.params['wait_time']
|
||||
|
||||
# Bizarre bug in libcloud when checking status after delete; socket.error is too generic to catch in this context so for now we don't even try.
|
||||
|
||||
try:
|
||||
return self.driver.connection.wait_for_state(
|
||||
state_to_wait_for,
|
||||
self.driver.ex_get_vlan,
|
||||
wait_poll_interval,
|
||||
wait_time,
|
||||
vlan
|
||||
)
|
||||
|
||||
except DimensionDataAPIException as api_exception:
|
||||
if api_exception.code != 'RESOURCE_NOT_FOUND':
|
||||
raise
|
||||
|
||||
return DimensionDataVlan(
|
||||
id=vlan.id,
|
||||
status='NOT_FOUND',
|
||||
name='',
|
||||
description='',
|
||||
private_ipv4_range_address='',
|
||||
private_ipv4_range_size=0,
|
||||
ipv4_gateway='',
|
||||
ipv6_range_address='',
|
||||
ipv6_range_size=0,
|
||||
ipv6_gateway='',
|
||||
location=self.location,
|
||||
network_domain=network_domain
|
||||
)
|
||||
|
||||
def _get_network_domain(self):
|
||||
"""
|
||||
Retrieve the target network domain from the Cloud Control API.
|
||||
|
||||
:return: The network domain.
|
||||
"""
|
||||
|
||||
try:
|
||||
return self.get_network_domain(
|
||||
self.network_domain_selector, self.location
|
||||
)
|
||||
except UnknownNetworkError:
|
||||
self.module.fail_json(
|
||||
msg='Cannot find network domain "{0}" in datacenter "{1}".'.format(
|
||||
self.network_domain_selector, self.location
|
||||
)
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class InvalidVlanChangeError(Exception):
|
||||
"""
|
||||
Error raised when an illegal change to VLAN state is attempted.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class VlanDiff(object):
|
||||
"""
|
||||
Represents differences between VLAN information (from CloudControl) and module parameters.
|
||||
"""
|
||||
|
||||
def __init__(self, vlan, module_params):
|
||||
"""
|
||||
|
||||
:param vlan: The VLAN information from CloudControl.
|
||||
:type vlan: DimensionDataVlan
|
||||
:param module_params: The module parameters.
|
||||
:type module_params: dict
|
||||
"""
|
||||
|
||||
self.vlan = vlan
|
||||
self.module_params = module_params
|
||||
|
||||
self.name_changed = module_params['name'] != vlan.name
|
||||
self.description_changed = module_params['description'] != vlan.description
|
||||
self.private_ipv4_base_address_changed = module_params['private_ipv4_base_address'] != vlan.private_ipv4_range_address
|
||||
self.private_ipv4_prefix_size_changed = module_params['private_ipv4_prefix_size'] != vlan.private_ipv4_range_size
|
||||
|
||||
# Is configured prefix size greater than or less than the actual prefix size?
|
||||
private_ipv4_prefix_size_difference = module_params['private_ipv4_prefix_size'] - vlan.private_ipv4_range_size
|
||||
self.private_ipv4_prefix_size_increased = private_ipv4_prefix_size_difference > 0
|
||||
self.private_ipv4_prefix_size_decreased = private_ipv4_prefix_size_difference < 0
|
||||
|
||||
def has_changes(self):
|
||||
"""
|
||||
Does the VlanDiff represent any changes between the VLAN and module configuration?
|
||||
|
||||
:return: True, if there are change changes; otherwise, False.
|
||||
"""
|
||||
|
||||
return self.needs_edit() or self.needs_expand()
|
||||
|
||||
def ensure_legal_change(self):
|
||||
"""
|
||||
Ensure the change (if any) represented by the VlanDiff represents a legal change to VLAN state.
|
||||
|
||||
- private_ipv4_base_address cannot be changed
|
||||
- private_ipv4_prefix_size must be greater than or equal to the VLAN's existing private_ipv4_range_size
|
||||
|
||||
:raise InvalidVlanChangeError: The VlanDiff does not represent a legal change to VLAN state.
|
||||
"""
|
||||
|
||||
# Cannot change base address for private IPv4 network.
|
||||
if self.private_ipv4_base_address_changed:
|
||||
raise InvalidVlanChangeError('Cannot change the private IPV4 base address for an existing VLAN.')
|
||||
|
||||
# Cannot shrink private IPv4 network (by increasing prefix size).
|
||||
if self.private_ipv4_prefix_size_increased:
|
||||
raise InvalidVlanChangeError('Cannot shrink the private IPV4 network for an existing VLAN (only expand is supported).')
|
||||
|
||||
def needs_edit(self):
|
||||
"""
|
||||
Is an Edit operation required to resolve the differences between the VLAN information and the module parameters?
|
||||
|
||||
:return: True, if an Edit operation is required; otherwise, False.
|
||||
"""
|
||||
|
||||
return self.name_changed or self.description_changed
|
||||
|
||||
def needs_expand(self):
|
||||
"""
|
||||
Is an Expand operation required to resolve the differences between the VLAN information and the module parameters?
|
||||
|
||||
The VLAN's network is expanded by reducing the size of its network prefix.
|
||||
|
||||
:return: True, if an Expand operation is required; otherwise, False.
|
||||
"""
|
||||
|
||||
return self.private_ipv4_prefix_size_decreased
|
||||
|
||||
|
||||
def vlan_to_dict(vlan):
|
||||
return {
|
||||
'id': vlan.id,
|
||||
'name': vlan.name,
|
||||
'description': vlan.description,
|
||||
'location': vlan.location.id,
|
||||
'private_ipv4_base_address': vlan.private_ipv4_range_address,
|
||||
'private_ipv4_prefix_size': vlan.private_ipv4_range_size,
|
||||
'private_ipv4_gateway_address': vlan.ipv4_gateway,
|
||||
'ipv6_base_address': vlan.ipv6_range_address,
|
||||
'ipv6_prefix_size': vlan.ipv6_range_size,
|
||||
'ipv6_gateway_address': vlan.ipv6_gateway,
|
||||
'status': vlan.status
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
module = DimensionDataVlanModule()
|
||||
|
||||
if module.state == 'present':
|
||||
module.state_present()
|
||||
elif module.state == 'readonly':
|
||||
module.state_readonly()
|
||||
elif module.state == 'absent':
|
||||
module.state_absent()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Loading…
Add table
Add a link
Reference in a new issue