mirror of https://github.com/ansible/ansible.git
Remove incidental_nios_txt_record (#72009)
* Add explicit coverage of argspec type=dict * Non string mapping failure * ci_complete ci_coverage * Remove incidental_nios_txt_record and associated files * Don't forget the ignore.txt changes * ci_complete ci_coveragepull/72036/head
parent
4197666179
commit
6f4aed5377
@ -1 +0,0 @@
|
||||
hidden
|
@ -1,3 +0,0 @@
|
||||
shippable/cloud/incidental
|
||||
cloud/nios
|
||||
destructive
|
@ -1,3 +0,0 @@
|
||||
---
|
||||
testcase: "*"
|
||||
test_items: []
|
@ -1,2 +0,0 @@
|
||||
dependencies:
|
||||
- incidental_nios_prepare_tests
|
@ -1 +0,0 @@
|
||||
- include: nios_txt_record_idempotence.yml
|
@ -1,80 +0,0 @@
|
||||
- name: cleanup the parent object
|
||||
nios_zone:
|
||||
name: ansible.com
|
||||
state: absent
|
||||
provider: "{{ nios_provider }}"
|
||||
|
||||
- name: create the parent object
|
||||
nios_zone:
|
||||
name: ansible.com
|
||||
state: present
|
||||
provider: "{{ nios_provider }}"
|
||||
|
||||
- name: cleanup txt record
|
||||
nios_txt_record:
|
||||
name: txt.ansible.com
|
||||
text: mytext
|
||||
state: absent
|
||||
provider: "{{ nios_provider }}"
|
||||
|
||||
- name: create txt record
|
||||
nios_txt_record:
|
||||
name: txt.ansible.com
|
||||
text: mytext
|
||||
state: present
|
||||
provider: "{{ nios_provider }}"
|
||||
register: txt_create1
|
||||
|
||||
- name: create txt record
|
||||
nios_txt_record:
|
||||
name: txt.ansible.com
|
||||
text: mytext
|
||||
state: present
|
||||
provider: "{{ nios_provider }}"
|
||||
register: txt_create2
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "txt_create1.changed"
|
||||
- "not txt_create2.changed"
|
||||
|
||||
- name: add a comment to an existing txt record
|
||||
nios_txt_record:
|
||||
name: txt.ansible.com
|
||||
text: mytext
|
||||
state: present
|
||||
comment: mycomment
|
||||
provider: "{{ nios_provider }}"
|
||||
register: txt_update1
|
||||
|
||||
- name: add a comment to an existing txt record
|
||||
nios_txt_record:
|
||||
name: txt.ansible.com
|
||||
text: mytext
|
||||
state: present
|
||||
comment: mycomment
|
||||
provider: "{{ nios_provider }}"
|
||||
register: txt_update2
|
||||
|
||||
- name: remove a txt record from the system
|
||||
nios_txt_record:
|
||||
name: txt.ansible.com
|
||||
state: absent
|
||||
provider: "{{ nios_provider }}"
|
||||
register: txt_delete1
|
||||
|
||||
- name: remove a txt record from the system
|
||||
nios_txt_record:
|
||||
name: txt.ansible.com
|
||||
state: absent
|
||||
provider: "{{ nios_provider }}"
|
||||
register: txt_delete2
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "txt_create1.changed"
|
||||
- "not txt_create2.changed"
|
||||
- "txt_update1.changed"
|
||||
- "not txt_update2.changed"
|
||||
- "txt_delete1.changed"
|
||||
- "not txt_delete2.changed"
|
@ -1,601 +0,0 @@
|
||||
# This code is part of Ansible, but is an independent component.
|
||||
# This particular file snippet, and this file snippet only, is BSD licensed.
|
||||
# Modules you write using this snippet, which is embedded dynamically by Ansible
|
||||
# still belong to the author of the module, and may assign their own license
|
||||
# to the complete work.
|
||||
#
|
||||
# (c) 2018 Red Hat Inc.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
||||
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
|
||||
import os
|
||||
from functools import partial
|
||||
from ansible.module_utils._text import to_native
|
||||
from ansible.module_utils.six import iteritems
|
||||
from ansible.module_utils._text import to_text
|
||||
from ansible.module_utils.basic import env_fallback
|
||||
|
||||
try:
|
||||
from infoblox_client.connector import Connector
|
||||
from infoblox_client.exceptions import InfobloxException
|
||||
HAS_INFOBLOX_CLIENT = True
|
||||
except ImportError:
|
||||
HAS_INFOBLOX_CLIENT = False
|
||||
|
||||
# defining nios constants
|
||||
NIOS_DNS_VIEW = 'view'
|
||||
NIOS_NETWORK_VIEW = 'networkview'
|
||||
NIOS_HOST_RECORD = 'record:host'
|
||||
NIOS_IPV4_NETWORK = 'network'
|
||||
NIOS_IPV6_NETWORK = 'ipv6network'
|
||||
NIOS_ZONE = 'zone_auth'
|
||||
NIOS_PTR_RECORD = 'record:ptr'
|
||||
NIOS_A_RECORD = 'record:a'
|
||||
NIOS_AAAA_RECORD = 'record:aaaa'
|
||||
NIOS_CNAME_RECORD = 'record:cname'
|
||||
NIOS_MX_RECORD = 'record:mx'
|
||||
NIOS_SRV_RECORD = 'record:srv'
|
||||
NIOS_NAPTR_RECORD = 'record:naptr'
|
||||
NIOS_TXT_RECORD = 'record:txt'
|
||||
NIOS_NSGROUP = 'nsgroup'
|
||||
NIOS_IPV4_FIXED_ADDRESS = 'fixedaddress'
|
||||
NIOS_IPV6_FIXED_ADDRESS = 'ipv6fixedaddress'
|
||||
NIOS_NEXT_AVAILABLE_IP = 'func:nextavailableip'
|
||||
NIOS_IPV4_NETWORK_CONTAINER = 'networkcontainer'
|
||||
NIOS_IPV6_NETWORK_CONTAINER = 'ipv6networkcontainer'
|
||||
NIOS_MEMBER = 'member'
|
||||
|
||||
NIOS_PROVIDER_SPEC = {
|
||||
'host': dict(fallback=(env_fallback, ['INFOBLOX_HOST'])),
|
||||
'username': dict(fallback=(env_fallback, ['INFOBLOX_USERNAME'])),
|
||||
'password': dict(fallback=(env_fallback, ['INFOBLOX_PASSWORD']), no_log=True),
|
||||
'validate_certs': dict(type='bool', default=False, fallback=(env_fallback, ['INFOBLOX_SSL_VERIFY']), aliases=['ssl_verify']),
|
||||
'silent_ssl_warnings': dict(type='bool', default=True),
|
||||
'http_request_timeout': dict(type='int', default=10, fallback=(env_fallback, ['INFOBLOX_HTTP_REQUEST_TIMEOUT'])),
|
||||
'http_pool_connections': dict(type='int', default=10),
|
||||
'http_pool_maxsize': dict(type='int', default=10),
|
||||
'max_retries': dict(type='int', default=3, fallback=(env_fallback, ['INFOBLOX_MAX_RETRIES'])),
|
||||
'wapi_version': dict(default='2.1', fallback=(env_fallback, ['INFOBLOX_WAP_VERSION'])),
|
||||
'max_results': dict(type='int', default=1000, fallback=(env_fallback, ['INFOBLOX_MAX_RETRIES']))
|
||||
}
|
||||
|
||||
|
||||
def get_connector(*args, **kwargs):
|
||||
''' Returns an instance of infoblox_client.connector.Connector
|
||||
:params args: positional arguments are silently ignored
|
||||
:params kwargs: dict that is passed to Connector init
|
||||
:returns: Connector
|
||||
'''
|
||||
if not HAS_INFOBLOX_CLIENT:
|
||||
raise Exception('infoblox-client is required but does not appear '
|
||||
'to be installed. It can be installed using the '
|
||||
'command `pip install infoblox-client`')
|
||||
|
||||
if not set(kwargs.keys()).issubset(list(NIOS_PROVIDER_SPEC.keys()) + ['ssl_verify']):
|
||||
raise Exception('invalid or unsupported keyword argument for connector')
|
||||
for key, value in iteritems(NIOS_PROVIDER_SPEC):
|
||||
if key not in kwargs:
|
||||
# apply default values from NIOS_PROVIDER_SPEC since we cannot just
|
||||
# assume the provider values are coming from AnsibleModule
|
||||
if 'default' in value:
|
||||
kwargs[key] = value['default']
|
||||
|
||||
# override any values with env variables unless they were
|
||||
# explicitly set
|
||||
env = ('INFOBLOX_%s' % key).upper()
|
||||
if env in os.environ:
|
||||
kwargs[key] = os.environ.get(env)
|
||||
|
||||
if 'validate_certs' in kwargs.keys():
|
||||
kwargs['ssl_verify'] = kwargs['validate_certs']
|
||||
kwargs.pop('validate_certs', None)
|
||||
|
||||
return Connector(kwargs)
|
||||
|
||||
|
||||
def normalize_extattrs(value):
|
||||
''' Normalize extattrs field to expected format
|
||||
The module accepts extattrs as key/value pairs. This method will
|
||||
transform the key/value pairs into a structure suitable for
|
||||
sending across WAPI in the format of:
|
||||
extattrs: {
|
||||
key: {
|
||||
value: <value>
|
||||
}
|
||||
}
|
||||
'''
|
||||
return dict([(k, {'value': v}) for k, v in iteritems(value)])
|
||||
|
||||
|
||||
def flatten_extattrs(value):
|
||||
''' Flatten the key/value struct for extattrs
|
||||
WAPI returns extattrs field as a dict in form of:
|
||||
extattrs: {
|
||||
key: {
|
||||
value: <value>
|
||||
}
|
||||
}
|
||||
This method will flatten the structure to:
|
||||
extattrs: {
|
||||
key: value
|
||||
}
|
||||
'''
|
||||
return dict([(k, v['value']) for k, v in iteritems(value)])
|
||||
|
||||
|
||||
def member_normalize(member_spec):
|
||||
''' Transforms the member module arguments into a valid WAPI struct
|
||||
This function will transform the arguments into a structure that
|
||||
is a valid WAPI structure in the format of:
|
||||
{
|
||||
key: <value>,
|
||||
}
|
||||
It will remove any arguments that are set to None since WAPI will error on
|
||||
that condition.
|
||||
The remainder of the value validation is performed by WAPI
|
||||
Some parameters in ib_spec are passed as a list in order to pass the validation for elements.
|
||||
In this function, they are converted to dictionary.
|
||||
'''
|
||||
member_elements = ['vip_setting', 'ipv6_setting', 'lan2_port_setting', 'mgmt_port_setting',
|
||||
'pre_provisioning', 'network_setting', 'v6_network_setting',
|
||||
'ha_port_setting', 'lan_port_setting', 'lan2_physical_setting',
|
||||
'lan_ha_port_setting', 'mgmt_network_setting', 'v6_mgmt_network_setting']
|
||||
for key in member_spec.keys():
|
||||
if key in member_elements and member_spec[key] is not None:
|
||||
member_spec[key] = member_spec[key][0]
|
||||
if isinstance(member_spec[key], dict):
|
||||
member_spec[key] = member_normalize(member_spec[key])
|
||||
elif isinstance(member_spec[key], list):
|
||||
for x in member_spec[key]:
|
||||
if isinstance(x, dict):
|
||||
x = member_normalize(x)
|
||||
elif member_spec[key] is None:
|
||||
del member_spec[key]
|
||||
return member_spec
|
||||
|
||||
|
||||
class WapiBase(object):
|
||||
''' Base class for implementing Infoblox WAPI API '''
|
||||
provider_spec = {'provider': dict(type='dict', options=NIOS_PROVIDER_SPEC)}
|
||||
|
||||
def __init__(self, provider):
|
||||
self.connector = get_connector(**provider)
|
||||
|
||||
def __getattr__(self, name):
|
||||
try:
|
||||
return self.__dict__[name]
|
||||
except KeyError:
|
||||
if name.startswith('_'):
|
||||
raise AttributeError("'%s' object has no attribute '%s'" % (self.__class__.__name__, name))
|
||||
return partial(self._invoke_method, name)
|
||||
|
||||
def _invoke_method(self, name, *args, **kwargs):
|
||||
try:
|
||||
method = getattr(self.connector, name)
|
||||
return method(*args, **kwargs)
|
||||
except InfobloxException as exc:
|
||||
if hasattr(self, 'handle_exception'):
|
||||
self.handle_exception(name, exc)
|
||||
else:
|
||||
raise
|
||||
|
||||
|
||||
class WapiLookup(WapiBase):
|
||||
''' Implements WapiBase for lookup plugins '''
|
||||
def handle_exception(self, method_name, exc):
|
||||
if ('text' in exc.response):
|
||||
raise Exception(exc.response['text'])
|
||||
else:
|
||||
raise Exception(exc)
|
||||
|
||||
|
||||
class WapiInventory(WapiBase):
|
||||
''' Implements WapiBase for dynamic inventory script '''
|
||||
pass
|
||||
|
||||
|
||||
class WapiModule(WapiBase):
|
||||
''' Implements WapiBase for executing a NIOS module '''
|
||||
def __init__(self, module):
|
||||
self.module = module
|
||||
provider = module.params['provider']
|
||||
try:
|
||||
super(WapiModule, self).__init__(provider)
|
||||
except Exception as exc:
|
||||
self.module.fail_json(msg=to_text(exc))
|
||||
|
||||
def handle_exception(self, method_name, exc):
|
||||
''' Handles any exceptions raised
|
||||
This method will be called if an InfobloxException is raised for
|
||||
any call to the instance of Connector and also, in case of generic
|
||||
exception. This method will then gracefully fail the module.
|
||||
:args exc: instance of InfobloxException
|
||||
'''
|
||||
if ('text' in exc.response):
|
||||
self.module.fail_json(
|
||||
msg=exc.response['text'],
|
||||
type=exc.response['Error'].split(':')[0],
|
||||
code=exc.response.get('code'),
|
||||
operation=method_name
|
||||
)
|
||||
else:
|
||||
self.module.fail_json(msg=to_native(exc))
|
||||
|
||||
def run(self, ib_obj_type, ib_spec):
|
||||
''' Runs the module and performans configuration tasks
|
||||
:args ib_obj_type: the WAPI object type to operate against
|
||||
:args ib_spec: the specification for the WAPI object as a dict
|
||||
:returns: a results dict
|
||||
'''
|
||||
|
||||
update = new_name = None
|
||||
state = self.module.params['state']
|
||||
if state not in ('present', 'absent'):
|
||||
self.module.fail_json(msg='state must be one of `present`, `absent`, got `%s`' % state)
|
||||
|
||||
result = {'changed': False}
|
||||
|
||||
obj_filter = dict([(k, self.module.params[k]) for k, v in iteritems(ib_spec) if v.get('ib_req')])
|
||||
|
||||
# get object reference
|
||||
ib_obj_ref, update, new_name = self.get_object_ref(self.module, ib_obj_type, obj_filter, ib_spec)
|
||||
proposed_object = {}
|
||||
for key, value in iteritems(ib_spec):
|
||||
if self.module.params[key] is not None:
|
||||
if 'transform' in value:
|
||||
proposed_object[key] = value['transform'](self.module)
|
||||
else:
|
||||
proposed_object[key] = self.module.params[key]
|
||||
|
||||
# If configure_by_dns is set to False, then delete the default dns set in the param else throw exception
|
||||
if not proposed_object.get('configure_for_dns') and proposed_object.get('view') == 'default'\
|
||||
and ib_obj_type == NIOS_HOST_RECORD:
|
||||
del proposed_object['view']
|
||||
elif not proposed_object.get('configure_for_dns') and proposed_object.get('view') != 'default'\
|
||||
and ib_obj_type == NIOS_HOST_RECORD:
|
||||
self.module.fail_json(msg='DNS Bypass is not allowed if DNS view is set other than \'default\'')
|
||||
|
||||
if ib_obj_ref:
|
||||
if len(ib_obj_ref) > 1:
|
||||
for each in ib_obj_ref:
|
||||
# To check for existing A_record with same name with input A_record by IP
|
||||
if each.get('ipv4addr') and each.get('ipv4addr') == proposed_object.get('ipv4addr'):
|
||||
current_object = each
|
||||
# To check for existing Host_record with same name with input Host_record by IP
|
||||
elif each.get('ipv4addrs')[0].get('ipv4addr') and each.get('ipv4addrs')[0].get('ipv4addr')\
|
||||
== proposed_object.get('ipv4addrs')[0].get('ipv4addr'):
|
||||
current_object = each
|
||||
# Else set the current_object with input value
|
||||
else:
|
||||
current_object = obj_filter
|
||||
ref = None
|
||||
else:
|
||||
current_object = ib_obj_ref[0]
|
||||
if 'extattrs' in current_object:
|
||||
current_object['extattrs'] = flatten_extattrs(current_object['extattrs'])
|
||||
if current_object.get('_ref'):
|
||||
ref = current_object.pop('_ref')
|
||||
else:
|
||||
current_object = obj_filter
|
||||
ref = None
|
||||
# checks if the object type is member to normalize the attributes being passed
|
||||
if (ib_obj_type == NIOS_MEMBER):
|
||||
proposed_object = member_normalize(proposed_object)
|
||||
|
||||
# checks if the name's field has been updated
|
||||
if update and new_name:
|
||||
proposed_object['name'] = new_name
|
||||
|
||||
check_remove = []
|
||||
if (ib_obj_type == NIOS_HOST_RECORD):
|
||||
# this check is for idempotency, as if the same ip address shall be passed
|
||||
# add param will be removed, and same exists true for remove case as well.
|
||||
if 'ipv4addrs' in [current_object and proposed_object]:
|
||||
for each in current_object['ipv4addrs']:
|
||||
if each['ipv4addr'] == proposed_object['ipv4addrs'][0]['ipv4addr']:
|
||||
if 'add' in proposed_object['ipv4addrs'][0]:
|
||||
del proposed_object['ipv4addrs'][0]['add']
|
||||
break
|
||||
check_remove += each.values()
|
||||
if proposed_object['ipv4addrs'][0]['ipv4addr'] not in check_remove:
|
||||
if 'remove' in proposed_object['ipv4addrs'][0]:
|
||||
del proposed_object['ipv4addrs'][0]['remove']
|
||||
|
||||
res = None
|
||||
modified = not self.compare_objects(current_object, proposed_object)
|
||||
if 'extattrs' in proposed_object:
|
||||
proposed_object['extattrs'] = normalize_extattrs(proposed_object['extattrs'])
|
||||
|
||||
# Checks if nios_next_ip param is passed in ipv4addrs/ipv4addr args
|
||||
proposed_object = self.check_if_nios_next_ip_exists(proposed_object)
|
||||
|
||||
if state == 'present':
|
||||
if ref is None:
|
||||
if not self.module.check_mode:
|
||||
self.create_object(ib_obj_type, proposed_object)
|
||||
result['changed'] = True
|
||||
# Check if NIOS_MEMBER and the flag to call function create_token is set
|
||||
elif (ib_obj_type == NIOS_MEMBER) and (proposed_object['create_token']):
|
||||
proposed_object = None
|
||||
# the function creates a token that can be used by a pre-provisioned member to join the grid
|
||||
result['api_results'] = self.call_func('create_token', ref, proposed_object)
|
||||
result['changed'] = True
|
||||
elif modified:
|
||||
if 'ipv4addrs' in proposed_object:
|
||||
if ('add' not in proposed_object['ipv4addrs'][0]) and ('remove' not in proposed_object['ipv4addrs'][0]):
|
||||
self.check_if_recordname_exists(obj_filter, ib_obj_ref, ib_obj_type, current_object, proposed_object)
|
||||
|
||||
if (ib_obj_type in (NIOS_HOST_RECORD, NIOS_NETWORK_VIEW, NIOS_DNS_VIEW)):
|
||||
run_update = True
|
||||
proposed_object = self.on_update(proposed_object, ib_spec)
|
||||
if 'ipv4addrs' in proposed_object:
|
||||
if ('add' or 'remove') in proposed_object['ipv4addrs'][0]:
|
||||
run_update, proposed_object = self.check_if_add_remove_ip_arg_exists(proposed_object)
|
||||
if run_update:
|
||||
res = self.update_object(ref, proposed_object)
|
||||
result['changed'] = True
|
||||
else:
|
||||
res = ref
|
||||
if (ib_obj_type in (NIOS_A_RECORD, NIOS_AAAA_RECORD, NIOS_PTR_RECORD, NIOS_SRV_RECORD)):
|
||||
# popping 'view' key as update of 'view' is not supported with respect to a:record/aaaa:record/srv:record/ptr:record
|
||||
proposed_object = self.on_update(proposed_object, ib_spec)
|
||||
del proposed_object['view']
|
||||
if not self.module.check_mode:
|
||||
res = self.update_object(ref, proposed_object)
|
||||
result['changed'] = True
|
||||
elif 'network_view' in proposed_object:
|
||||
proposed_object.pop('network_view')
|
||||
result['changed'] = True
|
||||
if not self.module.check_mode and res is None:
|
||||
proposed_object = self.on_update(proposed_object, ib_spec)
|
||||
self.update_object(ref, proposed_object)
|
||||
result['changed'] = True
|
||||
|
||||
elif state == 'absent':
|
||||
if ref is not None:
|
||||
if 'ipv4addrs' in proposed_object:
|
||||
if 'remove' in proposed_object['ipv4addrs'][0]:
|
||||
self.check_if_add_remove_ip_arg_exists(proposed_object)
|
||||
self.update_object(ref, proposed_object)
|
||||
result['changed'] = True
|
||||
elif not self.module.check_mode:
|
||||
self.delete_object(ref)
|
||||
result['changed'] = True
|
||||
|
||||
return result
|
||||
|
||||
def check_if_recordname_exists(self, obj_filter, ib_obj_ref, ib_obj_type, current_object, proposed_object):
|
||||
''' Send POST request if host record input name and retrieved ref name is same,
|
||||
but input IP and retrieved IP is different'''
|
||||
|
||||
if 'name' in (obj_filter and ib_obj_ref[0]) and ib_obj_type == NIOS_HOST_RECORD:
|
||||
obj_host_name = obj_filter['name']
|
||||
ref_host_name = ib_obj_ref[0]['name']
|
||||
if 'ipv4addrs' in (current_object and proposed_object):
|
||||
current_ip_addr = current_object['ipv4addrs'][0]['ipv4addr']
|
||||
proposed_ip_addr = proposed_object['ipv4addrs'][0]['ipv4addr']
|
||||
elif 'ipv6addrs' in (current_object and proposed_object):
|
||||
current_ip_addr = current_object['ipv6addrs'][0]['ipv6addr']
|
||||
proposed_ip_addr = proposed_object['ipv6addrs'][0]['ipv6addr']
|
||||
|
||||
if obj_host_name == ref_host_name and current_ip_addr != proposed_ip_addr:
|
||||
self.create_object(ib_obj_type, proposed_object)
|
||||
|
||||
def check_if_nios_next_ip_exists(self, proposed_object):
|
||||
''' Check if nios_next_ip argument is passed in ipaddr while creating
|
||||
host record, if yes then format proposed object ipv4addrs and pass
|
||||
func:nextavailableip and ipaddr range to create hostrecord with next
|
||||
available ip in one call to avoid any race condition '''
|
||||
|
||||
if 'ipv4addrs' in proposed_object:
|
||||
if 'nios_next_ip' in proposed_object['ipv4addrs'][0]['ipv4addr']:
|
||||
ip_range = self.module._check_type_dict(proposed_object['ipv4addrs'][0]['ipv4addr'])['nios_next_ip']
|
||||
proposed_object['ipv4addrs'][0]['ipv4addr'] = NIOS_NEXT_AVAILABLE_IP + ':' + ip_range
|
||||
elif 'ipv4addr' in proposed_object:
|
||||
if 'nios_next_ip' in proposed_object['ipv4addr']:
|
||||
ip_range = self.module._check_type_dict(proposed_object['ipv4addr'])['nios_next_ip']
|
||||
proposed_object['ipv4addr'] = NIOS_NEXT_AVAILABLE_IP + ':' + ip_range
|
||||
|
||||
return proposed_object
|
||||
|
||||
def check_if_add_remove_ip_arg_exists(self, proposed_object):
|
||||
'''
|
||||
This function shall check if add/remove param is set to true and
|
||||
is passed in the args, then we will update the proposed dictionary
|
||||
to add/remove IP to existing host_record, if the user passes false
|
||||
param with the argument nothing shall be done.
|
||||
:returns: True if param is changed based on add/remove, and also the
|
||||
changed proposed_object.
|
||||
'''
|
||||
update = False
|
||||
if 'add' in proposed_object['ipv4addrs'][0]:
|
||||
if proposed_object['ipv4addrs'][0]['add']:
|
||||
proposed_object['ipv4addrs+'] = proposed_object['ipv4addrs']
|
||||
del proposed_object['ipv4addrs']
|
||||
del proposed_object['ipv4addrs+'][0]['add']
|
||||
update = True
|
||||
else:
|
||||
del proposed_object['ipv4addrs'][0]['add']
|
||||
elif 'remove' in proposed_object['ipv4addrs'][0]:
|
||||
if proposed_object['ipv4addrs'][0]['remove']:
|
||||
proposed_object['ipv4addrs-'] = proposed_object['ipv4addrs']
|
||||
del proposed_object['ipv4addrs']
|
||||
del proposed_object['ipv4addrs-'][0]['remove']
|
||||
update = True
|
||||
else:
|
||||
del proposed_object['ipv4addrs'][0]['remove']
|
||||
return update, proposed_object
|
||||
|
||||
def issubset(self, item, objects):
|
||||
''' Checks if item is a subset of objects
|
||||
:args item: the subset item to validate
|
||||
:args objects: superset list of objects to validate against
|
||||
:returns: True if item is a subset of one entry in objects otherwise
|
||||
this method will return None
|
||||
'''
|
||||
for obj in objects:
|
||||
if isinstance(item, dict):
|
||||
if all(entry in obj.items() for entry in item.items()):
|
||||
return True
|
||||
else:
|
||||
if item in obj:
|
||||
return True
|
||||
|
||||
def compare_objects(self, current_object, proposed_object):
|
||||
for key, proposed_item in iteritems(proposed_object):
|
||||
current_item = current_object.get(key)
|
||||
|
||||
# if proposed has a key that current doesn't then the objects are
|
||||
# not equal and False will be immediately returned
|
||||
if current_item is None:
|
||||
return False
|
||||
|
||||
elif isinstance(proposed_item, list):
|
||||
for subitem in proposed_item:
|
||||
if not self.issubset(subitem, current_item):
|
||||
return False
|
||||
|
||||
elif isinstance(proposed_item, dict):
|
||||
return self.compare_objects(current_item, proposed_item)
|
||||
|
||||
else:
|
||||
if current_item != proposed_item:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def get_object_ref(self, module, ib_obj_type, obj_filter, ib_spec):
|
||||
''' this function gets the reference object of pre-existing nios objects '''
|
||||
|
||||
update = False
|
||||
old_name = new_name = None
|
||||
if ('name' in obj_filter):
|
||||
# gets and returns the current object based on name/old_name passed
|
||||
try:
|
||||
name_obj = self.module._check_type_dict(obj_filter['name'])
|
||||
old_name = name_obj['old_name']
|
||||
new_name = name_obj['new_name']
|
||||
except TypeError:
|
||||
name = obj_filter['name']
|
||||
|
||||
if old_name and new_name:
|
||||
if (ib_obj_type == NIOS_HOST_RECORD):
|
||||
test_obj_filter = dict([('name', old_name), ('view', obj_filter['view'])])
|
||||
elif (ib_obj_type in (NIOS_AAAA_RECORD, NIOS_A_RECORD)):
|
||||
test_obj_filter = obj_filter
|
||||
else:
|
||||
test_obj_filter = dict([('name', old_name)])
|
||||
# get the object reference
|
||||
ib_obj = self.get_object(ib_obj_type, test_obj_filter, return_fields=ib_spec.keys())
|
||||
if ib_obj:
|
||||
obj_filter['name'] = new_name
|
||||
else:
|
||||
test_obj_filter['name'] = new_name
|
||||
ib_obj = self.get_object(ib_obj_type, test_obj_filter, return_fields=ib_spec.keys())
|
||||
update = True
|
||||
return ib_obj, update, new_name
|
||||
if (ib_obj_type == NIOS_HOST_RECORD):
|
||||
# to check only by name if dns bypassing is set
|
||||
if not obj_filter['configure_for_dns']:
|
||||
test_obj_filter = dict([('name', name)])
|
||||
else:
|
||||
test_obj_filter = dict([('name', name), ('view', obj_filter['view'])])
|
||||
elif (ib_obj_type == NIOS_IPV4_FIXED_ADDRESS or ib_obj_type == NIOS_IPV6_FIXED_ADDRESS and 'mac' in obj_filter):
|
||||
test_obj_filter = dict([['mac', obj_filter['mac']]])
|
||||
elif (ib_obj_type == NIOS_A_RECORD):
|
||||
# resolves issue where a_record with uppercase name was returning null and was failing
|
||||
test_obj_filter = obj_filter
|
||||
test_obj_filter['name'] = test_obj_filter['name'].lower()
|
||||
# resolves issue where multiple a_records with same name and different IP address
|
||||
try:
|
||||
ipaddr_obj = self.module._check_type_dict(obj_filter['ipv4addr'])
|
||||
ipaddr = ipaddr_obj['old_ipv4addr']
|
||||
except TypeError:
|
||||
ipaddr = obj_filter['ipv4addr']
|
||||
test_obj_filter['ipv4addr'] = ipaddr
|
||||
elif (ib_obj_type == NIOS_TXT_RECORD):
|
||||
# resolves issue where multiple txt_records with same name and different text
|
||||
test_obj_filter = obj_filter
|
||||
try:
|
||||
text_obj = self.module._check_type_dict(obj_filter['text'])
|
||||
txt = text_obj['old_text']
|
||||
except TypeError:
|
||||
txt = obj_filter['text']
|
||||
test_obj_filter['text'] = txt
|
||||
# check if test_obj_filter is empty copy passed obj_filter
|
||||
else:
|
||||
test_obj_filter = obj_filter
|
||||
ib_obj = self.get_object(ib_obj_type, test_obj_filter.copy(), return_fields=ib_spec.keys())
|
||||
elif (ib_obj_type == NIOS_A_RECORD):
|
||||
# resolves issue where multiple a_records with same name and different IP address
|
||||
test_obj_filter = obj_filter
|
||||
try:
|
||||
ipaddr_obj = self.module._check_type_dict(obj_filter['ipv4addr'])
|
||||
ipaddr = ipaddr_obj['old_ipv4addr']
|
||||
except TypeError:
|
||||
ipaddr = obj_filter['ipv4addr']
|
||||
test_obj_filter['ipv4addr'] = ipaddr
|
||||
ib_obj = self.get_object(ib_obj_type, test_obj_filter.copy(), return_fields=ib_spec.keys())
|
||||
elif (ib_obj_type == NIOS_TXT_RECORD):
|
||||
# resolves issue where multiple txt_records with same name and different text
|
||||
test_obj_filter = obj_filter
|
||||
try:
|
||||
text_obj = self.module._check_type_dict(obj_filter['text'])
|
||||
txt = text_obj['old_text']
|
||||
except TypeError:
|
||||
txt = obj_filter['text']
|
||||
test_obj_filter['text'] = txt
|
||||
ib_obj = self.get_object(ib_obj_type, test_obj_filter.copy(), return_fields=ib_spec.keys())
|
||||
elif (ib_obj_type == NIOS_ZONE):
|
||||
# del key 'restart_if_needed' as nios_zone get_object fails with the key present
|
||||
temp = ib_spec['restart_if_needed']
|
||||
del ib_spec['restart_if_needed']
|
||||
ib_obj = self.get_object(ib_obj_type, obj_filter.copy(), return_fields=ib_spec.keys())
|
||||
# reinstate restart_if_needed if ib_obj is none, meaning there's no existing nios_zone ref
|
||||
if not ib_obj:
|
||||
ib_spec['restart_if_needed'] = temp
|
||||
elif (ib_obj_type == NIOS_MEMBER):
|
||||
# del key 'create_token' as nios_member get_object fails with the key present
|
||||
temp = ib_spec['create_token']
|
||||
del ib_spec['create_token']
|
||||
ib_obj = self.get_object(ib_obj_type, obj_filter.copy(), return_fields=ib_spec.keys())
|
||||
if temp:
|
||||
# reinstate 'create_token' key
|
||||
ib_spec['create_token'] = temp
|
||||
else:
|
||||
ib_obj = self.get_object(ib_obj_type, obj_filter.copy(), return_fields=ib_spec.keys())
|
||||
return ib_obj, update, new_name
|
||||
|
||||
def on_update(self, proposed_object, ib_spec):
|
||||
''' Event called before the update is sent to the API endpoing
|
||||
This method will allow the final proposed object to be changed
|
||||
and/or keys filtered before it is sent to the API endpoint to
|
||||
be processed.
|
||||
:args proposed_object: A dict item that will be encoded and sent
|
||||
the API endpoint with the updated data structure
|
||||
:returns: updated object to be sent to API endpoint
|
||||
'''
|
||||
keys = set()
|
||||
for key, value in iteritems(proposed_object):
|
||||
update = ib_spec[key].get('update', True)
|
||||
if not update:
|
||||
keys.add(key)
|
||||
return dict([(k, v) for k, v in iteritems(proposed_object) if k not in keys])
|
@ -1,134 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# Copyright (c) 2018 Red Hat, Inc.
|
||||
# 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': 'certified'}
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: nios_txt_record
|
||||
version_added: "2.7"
|
||||
author: "Corey Wanless (@coreywan)"
|
||||
short_description: Configure Infoblox NIOS txt records
|
||||
description:
|
||||
- Adds and/or removes instances of txt record objects from
|
||||
Infoblox NIOS servers. This module manages NIOS C(record:txt) objects
|
||||
using the Infoblox WAPI interface over REST.
|
||||
requirements:
|
||||
- infoblox_client
|
||||
extends_documentation_fragment: nios
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Specifies the fully qualified hostname to add or remove from
|
||||
the system
|
||||
required: true
|
||||
view:
|
||||
description:
|
||||
- Sets the DNS view to associate this tst record with. The DNS
|
||||
view must already be configured on the system
|
||||
required: true
|
||||
default: default
|
||||
aliases:
|
||||
- dns_view
|
||||
text:
|
||||
description:
|
||||
- Text associated with the record. It can contain up to 255 bytes
|
||||
per substring, up to a total of 512 bytes. To enter leading,
|
||||
trailing, or embedded spaces in the text, add quotes around the
|
||||
text to preserve the spaces.
|
||||
required: true
|
||||
ttl:
|
||||
description:
|
||||
- Configures the TTL to be associated with this tst record
|
||||
extattrs:
|
||||
description:
|
||||
- Allows for the configuration of Extensible Attributes on the
|
||||
instance of the object. This argument accepts a set of key / value
|
||||
pairs for configuration.
|
||||
comment:
|
||||
description:
|
||||
- Configures a text string comment to be associated with the instance
|
||||
of this object. The provided text string will be configured on the
|
||||
object instance.
|
||||
state:
|
||||
description:
|
||||
- Configures the intended state of the instance of the object on
|
||||
the NIOS server. When this value is set to C(present), the object
|
||||
is configured on the device and when this value is set to C(absent)
|
||||
the value is removed (if necessary) from the device.
|
||||
default: present
|
||||
choices:
|
||||
- present
|
||||
- absent
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Ensure a text Record Exists
|
||||
nios_txt_record:
|
||||
name: fqdn.txt.record.com
|
||||
text: mytext
|
||||
state: present
|
||||
view: External
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
|
||||
- name: Ensure a text Record does not exist
|
||||
nios_txt_record:
|
||||
name: fqdn.txt.record.com
|
||||
text: mytext
|
||||
state: absent
|
||||
view: External
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
'''
|
||||
|
||||
RETURN = ''' # '''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.six import iteritems
|
||||
from ansible.module_utils.net_tools.nios.api import WapiModule
|
||||
|
||||
|
||||
def main():
|
||||
''' Main entry point for module execution
|
||||
'''
|
||||
|
||||
ib_spec = dict(
|
||||
name=dict(required=True, ib_req=True),
|
||||
view=dict(default='default', aliases=['dns_view'], ib_req=True),
|
||||
text=dict(ib_req=True),
|
||||
ttl=dict(type='int'),
|
||||
extattrs=dict(type='dict'),
|
||||
comment=dict(),
|
||||
)
|
||||
|
||||
argument_spec = dict(
|
||||
provider=dict(required=True),
|
||||
state=dict(default='present', choices=['present', 'absent'])
|
||||
)
|
||||
|
||||
argument_spec.update(ib_spec)
|
||||
argument_spec.update(WapiModule.provider_spec)
|
||||
|
||||
module = AnsibleModule(argument_spec=argument_spec,
|
||||
supports_check_mode=True)
|
||||
|
||||
wapi = WapiModule(module)
|
||||
result = wapi.run('record:txt', ib_spec)
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -1,228 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# Copyright (c) 2018 Red Hat, Inc.
|
||||
# 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': 'certified'}
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: nios_zone
|
||||
version_added: "2.5"
|
||||
author: "Peter Sprygada (@privateip)"
|
||||
short_description: Configure Infoblox NIOS DNS zones
|
||||
description:
|
||||
- Adds and/or removes instances of DNS zone objects from
|
||||
Infoblox NIOS servers. This module manages NIOS C(zone_auth) objects
|
||||
using the Infoblox WAPI interface over REST.
|
||||
requirements:
|
||||
- infoblox-client
|
||||
extends_documentation_fragment: nios
|
||||
options:
|
||||
fqdn:
|
||||
description:
|
||||
- Specifies the qualified domain name to either add or remove from
|
||||
the NIOS instance based on the configured C(state) value.
|
||||
required: true
|
||||
aliases:
|
||||
- name
|
||||
view:
|
||||
description:
|
||||
- Configures the DNS view name for the configured resource. The
|
||||
specified DNS zone must already exist on the running NIOS instance
|
||||
prior to configuring zones.
|
||||
required: true
|
||||
default: default
|
||||
aliases:
|
||||
- dns_view
|
||||
grid_primary:
|
||||
description:
|
||||
- Configures the grid primary servers for this zone.
|
||||
suboptions:
|
||||
name:
|
||||
description:
|
||||
- The name of the grid primary server
|
||||
grid_secondaries:
|
||||
description:
|
||||
- Configures the grid secondary servers for this zone.
|
||||
suboptions:
|
||||
name:
|
||||
description:
|
||||
- The name of the grid secondary server
|
||||
ns_group:
|
||||
version_added: "2.6"
|
||||
description:
|
||||
- Configures the name server group for this zone. Name server group is
|
||||
mutually exclusive with grid primary and grid secondaries.
|
||||
restart_if_needed:
|
||||
version_added: "2.6"
|
||||
description:
|
||||
- If set to true, causes the NIOS DNS service to restart and load the
|
||||
new zone configuration
|
||||
type: bool
|
||||
zone_format:
|
||||
version_added: "2.7"
|
||||
description:
|
||||
- Create an authorative Reverse-Mapping Zone which is an area of network
|
||||
space for which one or more name servers-primary and secondary-have the
|
||||
responsibility to respond to address-to-name queries. It supports
|
||||
reverse-mapping zones for both IPv4 and IPv6 addresses.
|
||||
default: FORWARD
|
||||
extattrs:
|
||||
description:
|
||||
- Allows for the configuration of Extensible Attributes on the
|
||||
instance of the object. This argument accepts a set of key / value
|
||||
pairs for configuration.
|
||||
comment:
|
||||
description:
|
||||
- Configures a text string comment to be associated with the instance
|
||||
of this object. The provided text string will be configured on the
|
||||
object instance.
|
||||
state:
|
||||
description:
|
||||
- Configures the intended state of the instance of the object on
|
||||
the NIOS server. When this value is set to C(present), the object
|
||||
is configured on the device and when this value is set to C(absent)
|
||||
the value is removed (if necessary) from the device.
|
||||
default: present
|
||||
choices:
|
||||
- present
|
||||
- absent
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: configure a zone on the system using grid primary and secondaries
|
||||
nios_zone:
|
||||
name: ansible.com
|
||||
grid_primary:
|
||||
- name: gridprimary.grid.com
|
||||
grid_secondaries:
|
||||
- name: gridsecondary1.grid.com
|
||||
- name: gridsecondary2.grid.com
|
||||
restart_if_needed: true
|
||||
state: present
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
- name: configure a zone on the system using a name server group
|
||||
nios_zone:
|
||||
name: ansible.com
|
||||
ns_group: examplensg
|
||||
restart_if_needed: true
|
||||
state: present
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
- name: configure a reverse mapping zone on the system using IPV4 zone format
|
||||
nios_zone:
|
||||
name: 10.10.10.0/24
|
||||
zone_format: IPV4
|
||||
state: present
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
- name: configure a reverse mapping zone on the system using IPV6 zone format
|
||||
nios_zone:
|
||||
name: 100::1/128
|
||||
zone_format: IPV6
|
||||
state: present
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
- name: update the comment and ext attributes for an existing zone
|
||||
nios_zone:
|
||||
name: ansible.com
|
||||
comment: this is an example comment
|
||||
extattrs:
|
||||
Site: west-dc
|
||||
state: present
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
- name: remove the dns zone
|
||||
nios_zone:
|
||||
name: ansible.com
|
||||
state: absent
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
- name: remove the reverse mapping dns zone from the system with IPV4 zone format
|
||||
nios_zone:
|
||||
name: 10.10.10.0/24
|
||||
zone_format: IPV4
|
||||
state: absent
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
'''
|
||||
|
||||
RETURN = ''' # '''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.net_tools.nios.api import WapiModule
|
||||
from ansible.module_utils.net_tools.nios.api import NIOS_ZONE
|
||||
|
||||
|
||||
def main():
|
||||
''' Main entry point for module execution
|
||||
'''
|
||||
grid_spec = dict(
|
||||
name=dict(required=True),
|
||||
)
|
||||
|
||||
ib_spec = dict(
|
||||
fqdn=dict(required=True, aliases=['name'], ib_req=True, update=False),
|
||||
zone_format=dict(default='FORWARD', aliases=['zone_format'], ib_req=False),
|
||||
view=dict(default='default', aliases=['dns_view'], ib_req=True),
|
||||
|
||||
grid_primary=dict(type='list', elements='dict', options=grid_spec),
|
||||
grid_secondaries=dict(type='list', elements='dict', options=grid_spec),
|
||||
ns_group=dict(),
|
||||
restart_if_needed=dict(type='bool'),
|
||||
|
||||
extattrs=dict(type='dict'),
|
||||
comment=dict()
|
||||
)
|
||||
|
||||
argument_spec = dict(
|
||||
provider=dict(required=True),
|
||||
state=dict(default='present', choices=['present', 'absent'])
|
||||
)
|
||||
|
||||
argument_spec.update(ib_spec)
|
||||
argument_spec.update(WapiModule.provider_spec)
|
||||
|
||||
module = AnsibleModule(argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
mutually_exclusive=[
|
||||
['ns_group', 'grid_primary'],
|
||||
['ns_group', 'grid_secondaries']
|
||||
])
|
||||
|
||||
wapi = WapiModule(module)
|
||||
result = wapi.run(NIOS_ZONE, ib_spec)
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Loading…
Reference in New Issue