1b38b7c500
With "extends_documentation_fragment: ['openstack.cloud.openstack']" it is not necessary to list required Python libraries in section 'requirements' of DOCUMENTATION docstring in modules. Ansible will merge requirements from doc fragments and DOCUMENTATION docstring which previously resulted in duplicates such as in server module [0]: * openstacksdk * openstacksdk >= 0.36, < 0.99.0 * python >= 3.6 When removing the 'requirements' section from server module, then Ansible will list openstacksdk once only: * openstacksdk >= 0.36, < 0.99.0 * python >= 3.6 To see what documentation Ansible will produce for server module run: ansible-doc --type module openstack.cloud.server [0] https://docs.ansible.com/ansible/latest/collections/openstack/\ cloud/server_module.html Change-Id: I727ed95ee480bb644b5a533f6a9526973677064c
364 lines
12 KiB
Python
364 lines
12 KiB
Python
#!/usr/bin/python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright (c) 2021 by Uemit Seren <uemit.seren@gmail.com>
|
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
|
|
DOCUMENTATION = r'''
|
|
---
|
|
module: subnet_pool
|
|
short_description: Create, update or delete a subnet pool from OpenStack
|
|
author: OpenStack Ansible SIG
|
|
description:
|
|
- Create, update or delete a subnet pool from OpenStack.
|
|
options:
|
|
address_scope:
|
|
description:
|
|
- ID or name of the address scope associated with this subnet pool.
|
|
type: str
|
|
default_prefix_length:
|
|
description:
|
|
- The prefix length to allocate when the cidr or prefixlen attributes
|
|
are omitted when creating a subnet.
|
|
type: int
|
|
default_quota:
|
|
description:
|
|
- A per-project quota on the prefix space that can be allocated
|
|
from the subnet pool for project subnets.
|
|
type: int
|
|
description:
|
|
description: The subnet pool description.
|
|
type: str
|
|
extra_specs:
|
|
description:
|
|
- Dictionary with extra key/value pairs passed to the API.
|
|
type: dict
|
|
is_default:
|
|
description:
|
|
- Whether this subnet pool is the default.
|
|
type: bool
|
|
is_shared:
|
|
description:
|
|
- Whether this subnet pool is shared or not.
|
|
- This attribute cannot be updated.
|
|
type: bool
|
|
aliases: ['shared']
|
|
maximum_prefix_length:
|
|
description:
|
|
- The maximum prefix length that can be allocated from the subnet pool.
|
|
type: int
|
|
minimum_prefix_length:
|
|
description:
|
|
- The minimum prefix length that can be allocated from the subnet pool.
|
|
type: int
|
|
name:
|
|
description:
|
|
- Name to be give to the subnet pool.
|
|
- This attribute cannot be updated.
|
|
required: true
|
|
type: str
|
|
prefixes:
|
|
description:
|
|
- Subnet pool prefixes in CIDR notation.
|
|
type: list
|
|
elements: str
|
|
project:
|
|
description:
|
|
- Name or ID of the project.
|
|
type: str
|
|
state:
|
|
description:
|
|
- Whether the subnet pool should be C(present) or C(absent).
|
|
choices: ['present', 'absent']
|
|
default: present
|
|
type: str
|
|
extends_documentation_fragment:
|
|
- openstack.cloud.openstack
|
|
'''
|
|
|
|
EXAMPLES = r'''
|
|
- name: Create an subnet pool.
|
|
openstack.cloud.subnet_pool:
|
|
cloud: mycloud
|
|
state: present
|
|
name: my_subnet_pool
|
|
prefixes:
|
|
- 10.10.10.0/24
|
|
|
|
- name: Create a subnet pool for a given project.
|
|
openstack.cloud.subnet_pool:
|
|
cloud: mycloud
|
|
state: present
|
|
name: my_subnet_pool
|
|
project: myproj
|
|
prefixes:
|
|
- 10.10.10.0/24
|
|
|
|
- name: Create a shared and default subnet pool in existing address scope
|
|
openstack.cloud.subnet_pool:
|
|
cloud: mycloud
|
|
state: present
|
|
name: my_subnet_pool
|
|
address_scope: my_adress_scope
|
|
is_default: True
|
|
default_quota: 10
|
|
maximum_prefix_length: 32
|
|
minimum_prefix_length: 8
|
|
default_prefix_length: 24
|
|
is_shared: True
|
|
prefixes:
|
|
- 10.10.10.0/8
|
|
|
|
- name: Delete subnet poool.
|
|
openstack.cloud.subnet_pool:
|
|
cloud: mycloud
|
|
state: absent
|
|
name: my_subnet_pool
|
|
'''
|
|
|
|
RETURN = r'''
|
|
subnet_pool:
|
|
description: Dictionary describing the subnet pool.
|
|
returned: On success when I(state) is C(present).
|
|
type: dict
|
|
contains:
|
|
address_scope_id:
|
|
description: The address scope ID.
|
|
type: str
|
|
sample: "861174b82b43463c9edc5202aadc60ef"
|
|
created_at:
|
|
description: Timestamp when the subnet pool was created.
|
|
type: str
|
|
sample: ""
|
|
default_prefix_length:
|
|
description: The length of the prefix to allocate when the cidr or
|
|
prefixlen attributes are omitted when creating a
|
|
subnet.
|
|
type: int
|
|
sample: 32
|
|
default_quota:
|
|
description: The per-project quota on the prefix space that can be
|
|
allocated from the subnet pool for project subnets.
|
|
type: int
|
|
sample: 22
|
|
description:
|
|
description: The subnet pool description.
|
|
type: str
|
|
sample: "My test subnet pool."
|
|
id:
|
|
description: Subnet Pool ID.
|
|
type: str
|
|
sample: "474acfe5-be34-494c-b339-50f06aa143e4"
|
|
ip_version:
|
|
description: The IP version of the subnet pool 4 or 6.
|
|
type: int
|
|
sample: 4
|
|
is_default:
|
|
description: Indicates whether this is the default subnet pool.
|
|
type: bool
|
|
sample: false
|
|
is_shared:
|
|
description: Indicates whether this subnet pool is shared across
|
|
all projects.
|
|
type: bool
|
|
sample: false
|
|
maximum_prefix_length:
|
|
description: The maximum prefix length that can be allocated from
|
|
the subnet pool.
|
|
type: int
|
|
sample: 22
|
|
minimum_prefix_length:
|
|
description: The minimum prefix length that can be allocated from
|
|
the subnet pool.
|
|
type: int
|
|
sample: 8
|
|
name:
|
|
description: Subnet Pool name.
|
|
type: str
|
|
sample: "my_subnet_pool"
|
|
prefixes:
|
|
description: A list of subnet prefixes that are assigned to the
|
|
subnet pool.
|
|
type: list
|
|
sample: ['10.10.20.0/24', '10.20.10.0/24']
|
|
project_id:
|
|
description: The ID of the project.
|
|
type: str
|
|
sample: "861174b82b43463c9edc5202aadc60ef"
|
|
revision_number:
|
|
description: Revision number of the subnet pool.
|
|
type: int
|
|
sample: 5
|
|
tags:
|
|
description: A list of associated tags.
|
|
returned: success
|
|
type: list
|
|
tenant_id:
|
|
description: The ID of the project. Deprecated.
|
|
type: str
|
|
sample: "861174b82b43463c9edc5202aadc60ef"
|
|
updated_at:
|
|
description: Timestamp when the subnet pool was last updated.
|
|
type: str
|
|
sample:
|
|
'''
|
|
|
|
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
|
|
|
|
|
|
class SubnetPoolModule(OpenStackModule):
|
|
argument_spec = dict(
|
|
address_scope=dict(),
|
|
default_prefix_length=dict(type='int'),
|
|
default_quota=dict(type='int'),
|
|
description=dict(),
|
|
extra_specs=dict(type='dict'),
|
|
is_default=dict(type='bool'),
|
|
is_shared=dict(type='bool', aliases=['shared']),
|
|
maximum_prefix_length=dict(type='int'),
|
|
minimum_prefix_length=dict(type='int'),
|
|
name=dict(required=True),
|
|
prefixes=dict(type='list', elements='str'),
|
|
project=dict(),
|
|
state=dict(default='present', choices=['absent', 'present']),
|
|
)
|
|
|
|
def run(self):
|
|
state = self.params['state']
|
|
|
|
name = self.params['name']
|
|
subnet_pool = self.conn.network.find_subnet_pool(name)
|
|
|
|
if self.ansible.check_mode:
|
|
self.exit_json(changed=self._will_change(state, subnet_pool))
|
|
|
|
if state == 'present' and not subnet_pool:
|
|
# Create subnet_pool
|
|
subnet_pool = self._create()
|
|
self.exit_json(changed=True,
|
|
subnet_pool=subnet_pool.to_dict(computed=False))
|
|
|
|
elif state == 'present' and subnet_pool:
|
|
# Update subnet_pool
|
|
update = self._build_update(subnet_pool)
|
|
if update:
|
|
subnet_pool = self._update(subnet_pool, update)
|
|
|
|
self.exit_json(changed=bool(update),
|
|
subnet_pool=subnet_pool.to_dict(computed=False))
|
|
|
|
elif state == 'absent' and subnet_pool:
|
|
# Delete subnet_pool
|
|
self._delete(subnet_pool)
|
|
self.exit_json(changed=True)
|
|
|
|
elif state == 'absent' and not subnet_pool:
|
|
# Do nothing
|
|
self.exit_json(changed=False)
|
|
|
|
def _build_update(self, subnet_pool):
|
|
update = {}
|
|
|
|
attributes = dict((k, self.params[k])
|
|
for k in ['default_prefix_length', 'default_quota',
|
|
'description', 'is_default',
|
|
'maximum_prefix_length',
|
|
'minimum_prefix_length']
|
|
if self.params[k] is not None
|
|
and self.params[k] != subnet_pool[k])
|
|
|
|
for k in ['prefixes']:
|
|
if self.params[k] is not None \
|
|
and set(self.params[k]) != set(subnet_pool[k]):
|
|
attributes[k] = self.params[k]
|
|
|
|
project_name_or_id = self.params['project']
|
|
if project_name_or_id is not None:
|
|
project = self.conn.identity.find_project(project_name_or_id,
|
|
ignore_missing=False)
|
|
if subnet_pool['project_id'] != project.id:
|
|
attributes['project_id'] = project.id
|
|
|
|
address_scope_name_or_id = self.params['address_scope']
|
|
if address_scope_name_or_id is not None:
|
|
address_scope = self.conn.network.find_address_scope(
|
|
address_scope_name_or_id, ignore_missing=False)
|
|
if subnet_pool['address_scope_id'] != address_scope.id:
|
|
attributes['address_scope_id'] = address_scope.id
|
|
|
|
extra_specs = self.params['extra_specs']
|
|
if extra_specs:
|
|
duplicate_keys = set(attributes.keys()) & set(extra_specs.keys())
|
|
if duplicate_keys:
|
|
raise ValueError('Duplicate key(s) in extra_specs: {0}'
|
|
.format(', '.join(list(duplicate_keys))))
|
|
for k, v in extra_specs.items():
|
|
if v != subnet_pool[k]:
|
|
attributes[k] = v
|
|
|
|
if attributes:
|
|
update['attributes'] = attributes
|
|
|
|
return update
|
|
|
|
def _create(self):
|
|
kwargs = dict((k, self.params[k])
|
|
for k in ['default_prefix_length', 'default_quota',
|
|
'description', 'is_default', 'is_shared',
|
|
'maximum_prefix_length',
|
|
'minimum_prefix_length', 'name', 'prefixes']
|
|
if self.params[k] is not None)
|
|
|
|
project_name_or_id = self.params['project']
|
|
if project_name_or_id is not None:
|
|
project = self.conn.identity.find_project(project_name_or_id,
|
|
ignore_missing=False)
|
|
kwargs['project_id'] = project.id
|
|
|
|
address_scope_name_or_id = self.params['address_scope']
|
|
if address_scope_name_or_id is not None:
|
|
address_scope = self.conn.network.find_address_scope(
|
|
address_scope_name_or_id, ignore_missing=False)
|
|
kwargs['address_scope_id'] = address_scope.id
|
|
|
|
extra_specs = self.params['extra_specs']
|
|
if extra_specs:
|
|
duplicate_keys = set(kwargs.keys()) & set(extra_specs.keys())
|
|
if duplicate_keys:
|
|
raise ValueError('Duplicate key(s) in extra_specs: {0}'
|
|
.format(', '.join(list(duplicate_keys))))
|
|
kwargs = dict(kwargs, **extra_specs)
|
|
|
|
return self.conn.network.create_subnet_pool(**kwargs)
|
|
|
|
def _delete(self, subnet_pool):
|
|
self.conn.network.delete_subnet_pool(subnet_pool.id)
|
|
|
|
def _update(self, subnet_pool, update):
|
|
attributes = update.get('attributes')
|
|
if attributes:
|
|
subnet_pool = self.conn.network.update_subnet_pool(subnet_pool.id,
|
|
**attributes)
|
|
|
|
return subnet_pool
|
|
|
|
def _will_change(self, state, subnet_pool):
|
|
if state == 'present' and not subnet_pool:
|
|
return True
|
|
elif state == 'present' and subnet_pool:
|
|
return bool(self._build_update(subnet_pool))
|
|
elif state == 'absent' and subnet_pool:
|
|
return True
|
|
else:
|
|
# state == 'absent' and not subnet_pool:
|
|
return False
|
|
|
|
|
|
def main():
|
|
module = SubnetPoolModule()
|
|
module()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|