Refactored dns_zone{,_info} modules

Change-Id: I030cc0566ae94edf340d33e1e00c20403acabb2d
This commit is contained in:
Jakob Meng
2022-11-24 14:13:15 +01:00
parent 7c536e69b3
commit 0e07b15f92
8 changed files with 436 additions and 402 deletions

View File

@@ -1,4 +0,0 @@
dns_zone_name: test.dns.zone.
recordset_name: testrecordset.test.dns.zone.
records: ['10.0.0.0']
updated_records: ['10.1.1.1']

View File

@@ -1,32 +0,0 @@
---
- name: Create dns zone
openstack.cloud.dns_zone:
cloud: "{{ cloud }}"
name: "{{ dns_zone_name }}"
zone_type: "primary"
email: test@example.net
register: dns_zone
- debug: var=dns_zone
- name: Update dns zone
openstack.cloud.dns_zone:
cloud: "{{ cloud }}"
name: "{{ dns_zone.zone.name }}"
description: "New descirption"
register: updated_dns_zone
- debug: var=updated_dns_zone
- name: Delete dns zone
openstack.cloud.dns_zone:
cloud: "{{ cloud }}"
name: "{{ updated_dns_zone.zone.name }}"
state: absent
register: deleted_dns_zone
- name: Verify dns zone
assert:
that:
- deleted_dns_zone is successful
- deleted_dns_zone is changed

View File

@@ -0,0 +1,17 @@
expected_fields:
- action
- attributes
- created_at
- description
- email
- id
- links
- masters
- name
- pool_id
- project_id
- serial
- status
- ttl
- type
- updated_at

View File

@@ -0,0 +1,67 @@
---
- name: Create dns zone
openstack.cloud.dns_zone:
cloud: "{{ cloud }}"
name: ansible.test.zone.
type: primary
email: test@example.net
register: dns_zone
- name: Assert return values of dns_zone module
assert:
that:
- dns_zone.zone.name == "ansible.test.zone."
- dns_zone.zone.type|lower == "primary"
- dns_zone.zone.email == "test@example.net"
# allow new fields to be introduced but prevent fields from being removed
- expected_fields|difference(dns_zone.zone.keys())|length == 0
- name: Update dns zone
openstack.cloud.dns_zone:
cloud: "{{ cloud }}"
name: ansible.test.zone.
description: "Another description"
register: dns_zone
- name: Assert return values of dns_zone module
assert:
that:
- dns_zone.zone.description == "Another description"
- name: Fetch all dns zones
openstack.cloud.dns_zone_info:
cloud: "{{ cloud }}"
register: dns_zones
- name: Assert return values of dns_zone_info module
assert:
that:
- dns_zones is not changed
- dns_zones | length > 0
# allow new fields to be introduced but prevent fields from being removed
- expected_fields|difference(dns_zones.zones[0].keys())|length == 0
- name: Fetch a dns zone by name
openstack.cloud.dns_zone_info:
cloud: "{{ cloud }}"
name: ansible.test.zone.
register: dns_zones
- name: Assert return values of dns_zone_info module
assert:
that:
- dns_zones is not changed
- dns_zones.zones | length == 1
- dns_zones.zones[0].id == dns_zone.zone.id
- name: Delete dns zone
openstack.cloud.dns_zone:
cloud: "{{ cloud }}"
name: ansible.test.zone.
state: absent
register: dns_zone
- name: Verify dns zone
assert:
that:
- dns_zone is changed

View File

@@ -1,49 +0,0 @@
---
- name: Set random prefix
set_fact:
prefix: "{{ 99999999 | random | to_uuid | hash('md5') }}"
- name: Create dns zone
openstack.cloud.dns_zone:
cloud: "{{ cloud }}"
name: "{{ (prefix + '.test.zone.') }}"
email: test@example.net
- name: Getting info about dns zones
openstack.cloud.dns_zone_info:
cloud: "{{ cloud }}"
register: zones
- name: assert result
assert:
that:
- zones is success
- zones is not changed
- zones | length > 0
- name: Getting info about created zone
openstack.cloud.dns_zone_info:
cloud: "{{ cloud }}"
name: "{{ (prefix + '.test.zone.') }}"
register: zone
- name: assert result
assert:
that:
- zone is success
- zone is not changed
- zone.zones | length == 1
- name: Assert keys exist
assert:
that:
- '["action", "attributes", "created_at", "description", "email",
"links", "masters", "name", "pool_id", "project_id", "serial",
"status", "ttl", "type", "updated_at", "id"] |
difference(zone.zones.0.keys()) | length == 0'
- name: Drop created dns zone
openstack.cloud.dns_zone:
cloud: "{{ cloud }}"
name: "{{ (prefix + '.test.zone.') }}"
state: absent

View File

@@ -10,10 +10,7 @@
- { role: compute_flavor, tags: compute_flavor } - { role: compute_flavor, tags: compute_flavor }
- { role: compute_flavor_access, tags: compute_flavor_access } - { role: compute_flavor_access, tags: compute_flavor_access }
- { role: config, tags: config } - { role: config, tags: config }
- { role: dns_zone_info, tags: dns_zone_info } - { role: dns_zone, tags: dns_zone }
- role: dns
tags: dns
when: sdk_version is version(0.28, '>=')
- { role: endpoint, tags: endpoint } - { role: endpoint, tags: endpoint }
- { role: federation_mapping, tags: federation_mapping } - { role: federation_mapping, tags: federation_mapping }
- { role: floating_ip, tags: floating_ip } - { role: floating_ip, tags: floating_ip }

View File

@@ -4,116 +4,161 @@
# Copyright (c) 2016 Hewlett-Packard Enterprise # Copyright (c) 2016 Hewlett-Packard Enterprise
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
DOCUMENTATION = ''' DOCUMENTATION = r'''
--- ---
module: dns_zone module: dns_zone
short_description: Manage OpenStack DNS zones short_description: Manage a OpenStack DNS zone.
author: OpenStack Ansible SIG author: OpenStack Ansible SIG
description: description:
- Manage OpenStack DNS zones. Zones can be created, deleted or - Create, delete or update a OpenStack DNS zone.
updated. Only the I(email), I(description), I(ttl) and I(masters) values
can be updated.
options: options:
name: description:
description: description:
- Zone name - Zone description.
required: true type: str
type: str email:
zone_type: description:
description: - Email of the zone owner.
- Zone type - Only applies if I(type) is C(primary).
choices: [primary, secondary] type: str
type: str masters:
email: description:
description: - Master nameservers
- Email of the zone owner (only applies if zone_type is primary) - Only applies if I(type) is C(secondary).
type: str type: list
description: elements: str
description: name:
- Zone description description:
type: str - Name of the DNS zone.
ttl: required: true
description: type: str
- TTL (Time To Live) value in seconds state:
type: int description:
masters: - Whether the zone should be C(present) or C(absent).
description: choices: ['present', 'absent']
- Master nameservers (only applies if zone_type is secondary) default: present
type: list type: str
elements: str ttl:
state: description:
description: - TTL (Time To Live) value in seconds.
- Should the resource be present or absent. type: int
choices: [present, absent] type:
default: present description:
type: str - Zone type.
- This attribute cannot be updated.
choices: ['primary', 'secondary']
type: str
aliases: ['zone_type']
requirements: requirements:
- "python >= 3.6" - "python >= 3.6"
- "openstacksdk" - "openstacksdk"
extends_documentation_fragment: extends_documentation_fragment:
- openstack.cloud.openstack - openstack.cloud.openstack
''' '''
EXAMPLES = ''' EXAMPLES = r'''
# Create a zone named "example.net" - name: Create DNS zone example.net.
- openstack.cloud.dns_zone: openstack.cloud.dns_zone:
cloud: mycloud cloud: mycloud
state: present state: present
name: example.net. name: example.net.
zone_type: primary type: primary
email: test@example.net email: test@example.net
description: Test zone description: Test zone
ttl: 3600 ttl: 3600
# Update the TTL on existing "example.net." zone - name: Set TTL on DNS zone example.net.
- openstack.cloud.dns_zone: openstack.cloud.dns_zone:
cloud: mycloud cloud: mycloud
state: present state: present
name: example.net. name: example.net.
ttl: 7200 ttl: 7200
# Delete zone named "example.net." - name: Delete zone example.net.
- openstack.cloud.dns_zone: openstack.cloud.dns_zone:
cloud: mycloud cloud: mycloud
state: absent state: absent
name: example.net. name: example.net.
''' '''
RETURN = ''' RETURN = r'''
zone: zone:
description: Dictionary describing the zone. description: Dictionary describing the zone.
returned: On success when I(state) is 'present'. returned: On success when I(state) is C(present).
type: complex type: dict
contains: contains:
id: action:
description: Unique zone ID description: Current action in progress on the resource.
type: str type: str
sample: "c1c530a3-3619-46f3-b0f6-236927b2618c" sample: "CREATE"
name: attributes:
description: Zone name description: Key value pairs of information about this zone, and the
type: str pool the user would like to place the zone in. This
sample: "example.net." information can be used by the scheduler to place zones on
type: the correct pool.
description: Zone type type: dict
type: str sample: {"tier": "gold", "ha": "true"}
sample: "PRIMARY" created_at:
email: description: Date / Time when resource was created.
description: Zone owner email type: str
type: str sample: "2014-07-07T18:25:31.275934"
sample: "test@example.net" description:
description: description: Description for this zone.
description: Zone description type: str
type: str sample: "This is an example zone."
sample: "Test description" email:
ttl: description: E-mail for the zone. Used in SOA records for the zone.
description: Zone TTL value type: str
type: int sample: "test@example.org"
sample: 3600 id:
masters: description: ID for the resource.
description: Zone master nameservers type: int
type: list sample: "a86dba58-0043-4cc6-a1bb-69d5e86f3ca3"
sample: [] links:
description: Links to the resource, and other related resources. When a
response has been broken into pages, we will include a next
link that should be followed to retrieve all results.
type: dict
sample: {"self": "https://127.0.0.1:9001/v2/zones/a86dba...d5e86f3ca3"}
masters:
description: The servers to slave from to get DNS information.
Mandatory for secondary zones.
type: list
sample: "[]"
name:
description: DNS Name for the zone.
type: str
sample: "test.test."
pool_id:
description: ID for the pool hosting this zone.
type: str
sample: "a86dba58-0043-4cc6-a1bb-69d5e86f3ca3"
project_id:
description: ID for the project that owns the resource.
type: str
sample: "4335d1f0-f793-11e2-b778-0800200c9a66"
serial:
description: Current serial number for the zone.
type: int
sample: 1404757531
status:
description: Status of the resource.
type: str
sample: "ACTIVE"
ttl:
description: TTL (Time to Live) for the zone.
type: int
sample: 7200
type:
description: Type of zone. PRIMARY is controlled by Designate,
SECONDARY zones are slaved from another DNS Server.
Defaults to PRIMARY.
type: str
sample: "PRIMARY"
updated_at:
description: Date / Time when resource last updated.
type: str
sample: "2014-07-07T18:25:31.275934"
''' '''
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
@@ -122,119 +167,116 @@ from ansible_collections.openstack.cloud.plugins.module_utils.openstack import O
class DnsZoneModule(OpenStackModule): class DnsZoneModule(OpenStackModule):
argument_spec = dict( argument_spec = dict(
name=dict(required=True),
zone_type=dict(choices=['primary', 'secondary']),
email=dict(),
description=dict(), description=dict(),
ttl=dict(type='int'), email=dict(),
masters=dict(type='list', elements='str'), masters=dict(type='list', elements='str'),
name=dict(required=True),
state=dict(default='present', choices=['absent', 'present']), state=dict(default='present', choices=['absent', 'present']),
ttl=dict(type='int'),
type=dict(choices=['primary', 'secondary'], aliases=['zone_type']),
) )
def _system_state_change(self, state, email, description, ttl, masters, zone): def run(self):
if state == 'present': state = self.params['state']
if not zone: name_or_id = self.params['name']
return True
if email is not None and zone.email != email:
return True
if description is not None and zone.description != description:
return True
if ttl is not None and zone.ttl != ttl:
return True
if masters is not None and zone.masters != masters:
return True
if state == 'absent' and zone:
return True
return False
def _wait(self, timeout, zone, state): zone = self.conn.dns.find_zone(name_or_id=name_or_id)
"""Wait for a zone to reach the desired state for the given state."""
if self.ansible.check_mode:
self.exit_json(changed=self._will_change(state, zone))
if state == 'present' and not zone:
# Create zone
zone = self._create()
self.exit_json(changed=True,
zone=zone.to_dict(computed=False))
elif state == 'present' and zone:
# Update zone
update = self._build_update(zone)
if update:
zone = self._update(zone, update)
self.exit_json(changed=bool(update),
zone=zone.to_dict(computed=False))
elif state == 'absent' and zone:
# Delete zone
self._delete(zone)
self.exit_json(changed=True)
elif state == 'absent' and not zone:
# Do nothing
self.exit_json(changed=False)
def _build_update(self, zone):
update = {}
attributes = dict((k, self.params[k])
for k in ['description', 'email', 'masters', 'ttl']
if self.params[k] is not None
and self.params[k] != zone[k])
if attributes:
update['attributes'] = attributes
return update
def _create(self):
kwargs = dict((k, self.params[k])
for k in ['description', 'email', 'masters', 'name',
'ttl', 'type']
if self.params[k] is not None)
if 'type' in kwargs:
# designate expects upper case PRIMARY or SECONDARY
kwargs['type'] = kwargs['type'].upper()
zone = self.conn.dns.create_zone(**kwargs)
if self.params['wait']:
self.sdk.resource.wait_for_status(
self.conn.dns, zone,
status='active',
failures=['error'],
wait=self.params['timeout'])
return zone
def _delete(self, zone):
self.conn.dns.delete_zone(zone.id)
for count in self.sdk.utils.iterate_timeout( for count in self.sdk.utils.iterate_timeout(
timeout, timeout=self.params['timeout'],
"Timeout waiting for zone to be %s" % state): message="Timeout waiting for zone to be absent"
):
if self.conn.dns.find_zone(zone.id) is None:
break
if (state == 'absent' and zone is None) or (state == 'present' and zone and zone.status == 'ACTIVE'): def _update(self, zone, update):
return attributes = update.get('attributes')
if attributes:
zone = self.conn.dns.update_zone(zone.id, **attributes)
try: if self.params['wait']:
zone = self.conn.get_zone(zone.id) self.sdk.resource.wait_for_status(
except Exception: self.conn.dns, zone,
continue status='active',
failures=['error'],
wait=self.params['timeout'])
if zone and zone.status == 'ERROR': return zone
self.fail_json(msg="Zone reached ERROR state while waiting for it to be %s" % state)
def run(self): def _will_change(self, state, zone):
if state == 'present' and not zone:
name = self.params['name'] return True
state = self.params['state'] elif state == 'present' and zone:
wait = self.params['wait'] return bool(self._build_update(zone))
timeout = self.params['timeout'] elif state == 'absent' and zone:
return True
zone = self.conn.get_zone(name) else:
# state == 'absent' and not zone:
if state == 'present': return False
zone_type = self.params['zone_type']
email = self.params['email']
description = self.params['description']
ttl = self.params['ttl']
masters = self.params['masters']
kwargs = {}
if email:
kwargs['email'] = email
if description:
kwargs['description'] = description
if ttl:
kwargs['ttl'] = ttl
if masters:
kwargs['masters'] = masters
if self.ansible.check_mode:
self.exit_json(changed=self._system_state_change(state, email,
description, ttl,
masters, zone))
if zone is None:
zone = self.conn.create_zone(
name=name, zone_type=zone_type, **kwargs)
changed = True
else:
if masters is None:
masters = []
pre_update_zone = zone
changed = self._system_state_change(state, email,
description, ttl,
masters, pre_update_zone)
if changed:
zone = self.conn.update_zone(
name, **kwargs)
if wait:
self._wait(timeout, zone, state)
self.exit_json(changed=changed, zone=zone)
elif state == 'absent':
if self.ansible.check_mode:
self.exit_json(changed=self._system_state_change(state, None,
None, None,
None, zone))
if zone is None:
changed = False
else:
self.conn.delete_zone(name)
changed = True
if wait:
self._wait(timeout, zone, state)
self.exit_json(changed=changed)
def main(): def main():

View File

@@ -5,7 +5,7 @@
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
DOCUMENTATION = ''' DOCUMENTATION = r'''
--- ---
module: dns_zone_info module: dns_zone_info
short_description: Getting information about dns zones short_description: Getting information about dns zones
@@ -13,115 +13,125 @@ author: OpenStack Ansible SIG
description: description:
- Getting information about dns zones. Output can be filtered. - Getting information about dns zones. Output can be filtered.
options: options:
name: description:
description: description:
- Zone name. - Zone description.
type: str type: str
type: email:
description: description:
- Zone type. - Email of the zone owner
choices: [primary, secondary] - Only applies to primary zones.
type: str type: str
email: name:
description: description:
- Email of the zone owner (only applies if zone_type is primary). - Zone name.
type: str type: str
description: ttl:
description: description:
- Zone description. - TTL (Time To Live) value in seconds.
type: str type: int
ttl: type:
description: description:
- TTL (Time To Live) value in seconds. - Zone type.
type: int choices: ['primary', 'secondary']
type: str
requirements: requirements:
- "python >= 3.6" - "python >= 3.6"
- "openstacksdk" - "openstacksdk"
extends_documentation_fragment: extends_documentation_fragment:
- openstack.cloud.openstack - openstack.cloud.openstack
''' '''
EXAMPLES = ''' EXAMPLES = r'''
# Create a zone named "example.net" - name: Fetch all DNS zones
- openstack.cloud.dns_zones: openstack.cloud.dns_zones:
cloud: devstack
- name: Fetch DNS zones by name
openstack.cloud.dns_zones:
cloud: devstack
name: ansible.test.zone.
''' '''
RETURN = ''' RETURN = r'''
zone: zones:
description: Dictionary describing the zone. description: Dictionary describing the DNS zones.
returned: On success when I(state) is 'present'. returned: On success when I(state) is C(present).
type: complex type: list
contains: elements: dict
action: contains:
description: Current action in progress on the resource. action:
type: str description: Current action in progress on the resource.
sample: "CREATE" type: str
attributes: sample: "CREATE"
description: Key:Value pairs of information about this zone, and the pool the user would like to place \ attributes:
the zone in. This information can be used by the scheduler to place zones on the correct pool. description: Key value pairs of information about this zone, and the
type: dict pool the user would like to place the zone in. This
sample: {"tier": "gold", "ha": "true"} information can be used by the scheduler to place zones on
created_at: the correct pool.
description: Date / Time when resource was created. type: dict
type: str sample: {"tier": "gold", "ha": "true"}
sample: "2014-07-07T18:25:31.275934" created_at:
description: description: Date / Time when resource was created.
description: Description for this zone. type: str
type: str sample: "2014-07-07T18:25:31.275934"
sample: "This is an example zone." description:
email: description: Description for this zone.
description: E-mail for the zone. Used in SOA records for the zone. type: str
type: str sample: "This is an example zone."
sample: "test@example.org" email:
id: description: E-mail for the zone. Used in SOA records for the zone.
description: ID for the resource. type: str
type: int sample: "test@example.org"
sample: "a86dba58-0043-4cc6-a1bb-69d5e86f3ca3" id:
links: description: ID for the resource.
description: Links to the resource, and other related resources. When a response has been broken into\ type: int
pages, we will include a next link that should be followed to retrieve all results. sample: "a86dba58-0043-4cc6-a1bb-69d5e86f3ca3"
type: dict links:
sample: {"self": "https://127.0.0.1:9001/v2/zones/a86dba58-0043-4cc6-a1bb-69d5e86f3ca3"} description: Links to the resource, and other related resources. When a
masters: response has been broken into pages, we will include a next
description: Mandatory for secondary zones. The servers to slave from to get DNS information. link that should be followed to retrieve all results.
type: list type: dict
sample: "[]" sample: {"self": "https://127.0.0.1:9001/v2/zones/a86dba...d5e86f3ca3"}
name: masters:
description: DNS Name for the zone. description: The servers to slave from to get DNS information.
type: str Mandatory for secondary zones.
sample: "test.test." type: list
pool_id: sample: "[]"
description: ID for the pool hosting this zone. name:
type: str description: DNS Name for the zone.
sample: "a86dba58-0043-4cc6-a1bb-69d5e86f3ca3" type: str
project_id: sample: "test.test."
description: ID for the project that owns the resource. pool_id:
type: str description: ID for the pool hosting this zone.
sample: "4335d1f0-f793-11e2-b778-0800200c9a66" type: str
serial: sample: "a86dba58-0043-4cc6-a1bb-69d5e86f3ca3"
description: Current serial number for the zone. project_id:
type: int description: ID for the project that owns the resource.
sample: 1404757531 type: str
status: sample: "4335d1f0-f793-11e2-b778-0800200c9a66"
description: Status of the resource. serial:
type: str description: Current serial number for the zone.
sample: "ACTIVE" type: int
ttl: sample: 1404757531
description: TTL (Time to Live) for the zone. status:
type: int description: Status of the resource.
sample: 7200 type: str
type: sample: "ACTIVE"
description: Type of zone. PRIMARY is controlled by Designate, SECONDARY zones are slaved from another\ ttl:
DNS Server. Defaults to PRIMARY description: TTL (Time to Live) for the zone.
type: str type: int
sample: "PRIMARY" sample: 7200
updated_at: type:
description: Date / Time when resource last updated. description: Type of zone. PRIMARY is controlled by Designate,
type: str SECONDARY zones are slaved from another DNS Server.
sample: "2014-07-07T18:25:31.275934" Defaults to PRIMARY.
type: str
sample: "PRIMARY"
updated_at:
description: Date / Time when resource last updated.
type: str
sample: "2014-07-07T18:25:31.275934"
''' '''
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
@@ -130,41 +140,27 @@ from ansible_collections.openstack.cloud.plugins.module_utils.openstack import O
class DnsZoneInfoModule(OpenStackModule): class DnsZoneInfoModule(OpenStackModule):
argument_spec = dict( argument_spec = dict(
name=dict(),
type=dict(choices=['primary', 'secondary']),
email=dict(),
description=dict(), description=dict(),
ttl=dict(type='int') email=dict(),
name=dict(),
ttl=dict(type='int'),
type=dict(choices=['primary', 'secondary']),
) )
module_kwargs = dict( module_kwargs = dict(
supports_check_mode=True supports_check_mode=True
) )
def run(self): def run(self):
kwargs = dict((k, self.params[k])
for k in ['description', 'email', 'name',
'ttl', 'type']
if self.params[k] is not None)
name = self.params['name'] zones = self.conn.dns.zones(**kwargs)
type = self.params['type']
email = self.params['email']
description = self.params['description']
ttl = self.params['ttl']
kwargs = {} self.exit_json(
changed=False,
if name: zones=[z.to_dict(computed=False) for z in zones])
kwargs['name'] = name
if type:
kwargs['type'] = type
if email:
kwargs['email'] = email
if description:
kwargs['description'] = description
if ttl:
kwargs['ttl'] = ttl
data = [zone.to_dict(computed=False) for zone in
self.conn.dns.zones(**kwargs)]
self.exit_json(zones=data, changed=False)
def main(): def main():