mirror of
https://github.com/ansible-collections/community.general.git
synced 2026-05-06 13:22:48 +00:00
Initial commit
This commit is contained in:
875
plugins/modules/net_tools/cloudflare_dns.py
Normal file
875
plugins/modules/net_tools/cloudflare_dns.py
Normal file
@@ -0,0 +1,875 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: cloudflare_dns
|
||||
author:
|
||||
- Michael Gruener (@mgruener)
|
||||
requirements:
|
||||
- python >= 2.6
|
||||
short_description: Manage Cloudflare DNS records
|
||||
description:
|
||||
- "Manages dns records via the Cloudflare API, see the docs: U(https://api.cloudflare.com/)"
|
||||
options:
|
||||
api_token:
|
||||
description:
|
||||
- API token.
|
||||
- Required for api token authentication.
|
||||
- "You can obtain your API token from the bottom of the Cloudflare 'My Account' page, found here: U(https://dash.cloudflare.com/)"
|
||||
type: str
|
||||
required: false
|
||||
account_api_key:
|
||||
description:
|
||||
- Account API key.
|
||||
- Required for api keys authentication.
|
||||
- "You can obtain your API key from the bottom of the Cloudflare 'My Account' page, found here: U(https://dash.cloudflare.com/)"
|
||||
type: str
|
||||
required: false
|
||||
aliases: [ account_api_token ]
|
||||
account_email:
|
||||
description:
|
||||
- Account email. Required for api keys authentication.
|
||||
type: str
|
||||
required: false
|
||||
algorithm:
|
||||
description:
|
||||
- Algorithm number.
|
||||
- Required for C(type=DS) and C(type=SSHFP) when C(state=present).
|
||||
type: int
|
||||
cert_usage:
|
||||
description:
|
||||
- Certificate usage number.
|
||||
- Required for C(type=TLSA) when C(state=present).
|
||||
type: int
|
||||
choices: [ 0, 1, 2, 3 ]
|
||||
hash_type:
|
||||
description:
|
||||
- Hash type number.
|
||||
- Required for C(type=DS), C(type=SSHFP) and C(type=TLSA) when C(state=present).
|
||||
type: int
|
||||
choices: [ 1, 2 ]
|
||||
key_tag:
|
||||
description:
|
||||
- DNSSEC key tag.
|
||||
- Needed for C(type=DS) when C(state=present).
|
||||
type: int
|
||||
port:
|
||||
description:
|
||||
- Service port.
|
||||
- Required for C(type=SRV) and C(type=TLSA).
|
||||
type: int
|
||||
priority:
|
||||
description:
|
||||
- Record priority.
|
||||
- Required for C(type=MX) and C(type=SRV)
|
||||
default: 1
|
||||
proto:
|
||||
description:
|
||||
- Service protocol. Required for C(type=SRV) and C(type=TLSA).
|
||||
- Common values are TCP and UDP.
|
||||
- Before Ansible 2.6 only TCP and UDP were available.
|
||||
type: str
|
||||
proxied:
|
||||
description:
|
||||
- Proxy through Cloudflare network or just use DNS.
|
||||
type: bool
|
||||
default: no
|
||||
record:
|
||||
description:
|
||||
- Record to add.
|
||||
- Required if C(state=present).
|
||||
- Default is C(@) (e.g. the zone name).
|
||||
type: str
|
||||
default: '@'
|
||||
aliases: [ name ]
|
||||
selector:
|
||||
description:
|
||||
- Selector number.
|
||||
- Required for C(type=TLSA) when C(state=present).
|
||||
choices: [ 0, 1 ]
|
||||
type: int
|
||||
service:
|
||||
description:
|
||||
- Record service.
|
||||
- Required for C(type=SRV)
|
||||
solo:
|
||||
description:
|
||||
- Whether the record should be the only one for that record type and record name.
|
||||
- Only use with C(state=present).
|
||||
- This will delete all other records with the same record name and type.
|
||||
type: bool
|
||||
state:
|
||||
description:
|
||||
- Whether the record(s) should exist or not.
|
||||
type: str
|
||||
choices: [ absent, present ]
|
||||
default: present
|
||||
timeout:
|
||||
description:
|
||||
- Timeout for Cloudflare API calls.
|
||||
type: int
|
||||
default: 30
|
||||
ttl:
|
||||
description:
|
||||
- The TTL to give the new record.
|
||||
- Must be between 120 and 2,147,483,647 seconds, or 1 for automatic.
|
||||
type: int
|
||||
default: 1
|
||||
type:
|
||||
description:
|
||||
- The type of DNS record to create. Required if C(state=present).
|
||||
- C(type=DS), C(type=SSHFP) and C(type=TLSA) added in Ansible 2.7.
|
||||
type: str
|
||||
choices: [ A, AAAA, CNAME, DS, MX, NS, SPF, SRV, SSHFP, TLSA, TXT ]
|
||||
value:
|
||||
description:
|
||||
- The record value.
|
||||
- Required for C(state=present).
|
||||
type: str
|
||||
aliases: [ content ]
|
||||
weight:
|
||||
description:
|
||||
- Service weight.
|
||||
- Required for C(type=SRV).
|
||||
type: int
|
||||
default: 1
|
||||
zone:
|
||||
description:
|
||||
- The name of the Zone to work with (e.g. "example.com").
|
||||
- The Zone must already exist.
|
||||
type: str
|
||||
required: true
|
||||
aliases: [ domain ]
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Create a test.example.net A record to point to 127.0.0.1
|
||||
cloudflare_dns:
|
||||
zone: example.net
|
||||
record: test
|
||||
type: A
|
||||
value: 127.0.0.1
|
||||
account_email: test@example.com
|
||||
account_api_key: dummyapitoken
|
||||
register: record
|
||||
|
||||
- name: Create a record using api token
|
||||
cloudflare_dns:
|
||||
zone: example.net
|
||||
record: test
|
||||
type: A
|
||||
value: 127.0.0.1
|
||||
api_token: dummyapitoken
|
||||
|
||||
- name: Create a example.net CNAME record to example.com
|
||||
cloudflare_dns:
|
||||
zone: example.net
|
||||
type: CNAME
|
||||
value: example.com
|
||||
account_email: test@example.com
|
||||
account_api_key: dummyapitoken
|
||||
state: present
|
||||
|
||||
- name: Change its TTL
|
||||
cloudflare_dns:
|
||||
zone: example.net
|
||||
type: CNAME
|
||||
value: example.com
|
||||
ttl: 600
|
||||
account_email: test@example.com
|
||||
account_api_key: dummyapitoken
|
||||
state: present
|
||||
|
||||
- name: Delete the record
|
||||
cloudflare_dns:
|
||||
zone: example.net
|
||||
type: CNAME
|
||||
value: example.com
|
||||
account_email: test@example.com
|
||||
account_api_key: dummyapitoken
|
||||
state: absent
|
||||
|
||||
- name: create a example.net CNAME record to example.com and proxy through Cloudflare's network
|
||||
cloudflare_dns:
|
||||
zone: example.net
|
||||
type: CNAME
|
||||
value: example.com
|
||||
proxied: yes
|
||||
account_email: test@example.com
|
||||
account_api_key: dummyapitoken
|
||||
state: present
|
||||
|
||||
# This deletes all other TXT records named "test.example.net"
|
||||
- name: Create TXT record "test.example.net" with value "unique value"
|
||||
cloudflare_dns:
|
||||
domain: example.net
|
||||
record: test
|
||||
type: TXT
|
||||
value: unique value
|
||||
solo: true
|
||||
account_email: test@example.com
|
||||
account_api_key: dummyapitoken
|
||||
state: present
|
||||
|
||||
- name: Create an SRV record _foo._tcp.example.net
|
||||
cloudflare_dns:
|
||||
domain: example.net
|
||||
service: foo
|
||||
proto: tcp
|
||||
port: 3500
|
||||
priority: 10
|
||||
weight: 20
|
||||
type: SRV
|
||||
value: fooserver.example.net
|
||||
|
||||
- name: Create a SSHFP record login.example.com
|
||||
cloudflare_dns:
|
||||
zone: example.com
|
||||
record: login
|
||||
type: SSHFP
|
||||
algorithm: 4
|
||||
hash_type: 2
|
||||
value: 9dc1d6742696d2f51ca1f1a78b3d16a840f7d111eb9454239e70db31363f33e1
|
||||
|
||||
- name: Create a TLSA record _25._tcp.mail.example.com
|
||||
cloudflare_dns:
|
||||
zone: example.com
|
||||
record: mail
|
||||
port: 25
|
||||
proto: tcp
|
||||
type: TLSA
|
||||
cert_usage: 3
|
||||
selector: 1
|
||||
hash_type: 1
|
||||
value: 6b76d034492b493e15a7376fccd08e63befdad0edab8e442562f532338364bf3
|
||||
|
||||
- name: Create a DS record for subdomain.example.com
|
||||
cloudflare_dns:
|
||||
zone: example.com
|
||||
record: subdomain
|
||||
type: DS
|
||||
key_tag: 5464
|
||||
algorithm: 8
|
||||
hash_type: 2
|
||||
value: B4EB5AC4467D2DFB3BAF9FB9961DC1B6FED54A58CDFAA3E465081EC86F89BFAB
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
record:
|
||||
description: A dictionary containing the record data.
|
||||
returned: success, except on record deletion
|
||||
type: complex
|
||||
contains:
|
||||
content:
|
||||
description: The record content (details depend on record type).
|
||||
returned: success
|
||||
type: str
|
||||
sample: 192.0.2.91
|
||||
created_on:
|
||||
description: The record creation date.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "2016-03-25T19:09:42.516553Z"
|
||||
data:
|
||||
description: Additional record data.
|
||||
returned: success, if type is SRV, DS, SSHFP or TLSA
|
||||
type: dict
|
||||
sample: {
|
||||
name: "jabber",
|
||||
port: 8080,
|
||||
priority: 10,
|
||||
proto: "_tcp",
|
||||
service: "_xmpp",
|
||||
target: "jabberhost.sample.com",
|
||||
weight: 5,
|
||||
}
|
||||
id:
|
||||
description: The record ID.
|
||||
returned: success
|
||||
type: str
|
||||
sample: f9efb0549e96abcb750de63b38c9576e
|
||||
locked:
|
||||
description: No documentation available.
|
||||
returned: success
|
||||
type: bool
|
||||
sample: False
|
||||
meta:
|
||||
description: No documentation available.
|
||||
returned: success
|
||||
type: dict
|
||||
sample: { auto_added: false }
|
||||
modified_on:
|
||||
description: Record modification date.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "2016-03-25T19:09:42.516553Z"
|
||||
name:
|
||||
description: The record name as FQDN (including _service and _proto for SRV).
|
||||
returned: success
|
||||
type: str
|
||||
sample: www.sample.com
|
||||
priority:
|
||||
description: Priority of the MX record.
|
||||
returned: success, if type is MX
|
||||
type: int
|
||||
sample: 10
|
||||
proxiable:
|
||||
description: Whether this record can be proxied through Cloudflare.
|
||||
returned: success
|
||||
type: bool
|
||||
sample: False
|
||||
proxied:
|
||||
description: Whether the record is proxied through Cloudflare.
|
||||
returned: success
|
||||
type: bool
|
||||
sample: False
|
||||
ttl:
|
||||
description: The time-to-live for the record.
|
||||
returned: success
|
||||
type: int
|
||||
sample: 300
|
||||
type:
|
||||
description: The record type.
|
||||
returned: success
|
||||
type: str
|
||||
sample: A
|
||||
zone_id:
|
||||
description: The ID of the zone containing the record.
|
||||
returned: success
|
||||
type: str
|
||||
sample: abcede0bf9f0066f94029d2e6b73856a
|
||||
zone_name:
|
||||
description: The name of the zone containing the record.
|
||||
returned: success
|
||||
type: str
|
||||
sample: sample.com
|
||||
'''
|
||||
|
||||
import json
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.six.moves.urllib.parse import urlencode
|
||||
from ansible.module_utils._text import to_native, to_text
|
||||
from ansible.module_utils.urls import fetch_url
|
||||
|
||||
|
||||
def lowercase_string(param):
|
||||
if not isinstance(param, str):
|
||||
return param
|
||||
return param.lower()
|
||||
|
||||
|
||||
class CloudflareAPI(object):
|
||||
|
||||
cf_api_endpoint = 'https://api.cloudflare.com/client/v4'
|
||||
changed = False
|
||||
|
||||
def __init__(self, module):
|
||||
self.module = module
|
||||
self.api_token = module.params['api_token']
|
||||
self.account_api_key = module.params['account_api_key']
|
||||
self.account_email = module.params['account_email']
|
||||
self.algorithm = module.params['algorithm']
|
||||
self.cert_usage = module.params['cert_usage']
|
||||
self.hash_type = module.params['hash_type']
|
||||
self.key_tag = module.params['key_tag']
|
||||
self.port = module.params['port']
|
||||
self.priority = module.params['priority']
|
||||
self.proto = lowercase_string(module.params['proto'])
|
||||
self.proxied = module.params['proxied']
|
||||
self.selector = module.params['selector']
|
||||
self.record = lowercase_string(module.params['record'])
|
||||
self.service = lowercase_string(module.params['service'])
|
||||
self.is_solo = module.params['solo']
|
||||
self.state = module.params['state']
|
||||
self.timeout = module.params['timeout']
|
||||
self.ttl = module.params['ttl']
|
||||
self.type = module.params['type']
|
||||
self.value = module.params['value']
|
||||
self.weight = module.params['weight']
|
||||
self.zone = lowercase_string(module.params['zone'])
|
||||
|
||||
if self.record == '@':
|
||||
self.record = self.zone
|
||||
|
||||
if (self.type in ['CNAME', 'NS', 'MX', 'SRV']) and (self.value is not None):
|
||||
self.value = self.value.rstrip('.').lower()
|
||||
|
||||
if (self.type == 'AAAA') and (self.value is not None):
|
||||
self.value = self.value.lower()
|
||||
|
||||
if (self.type == 'SRV'):
|
||||
if (self.proto is not None) and (not self.proto.startswith('_')):
|
||||
self.proto = '_' + self.proto
|
||||
if (self.service is not None) and (not self.service.startswith('_')):
|
||||
self.service = '_' + self.service
|
||||
|
||||
if (self.type == 'TLSA'):
|
||||
if (self.proto is not None) and (not self.proto.startswith('_')):
|
||||
self.proto = '_' + self.proto
|
||||
if (self.port is not None):
|
||||
self.port = '_' + str(self.port)
|
||||
|
||||
if not self.record.endswith(self.zone):
|
||||
self.record = self.record + '.' + self.zone
|
||||
|
||||
if (self.type == 'DS'):
|
||||
if self.record == self.zone:
|
||||
self.module.fail_json(msg="DS records only apply to subdomains.")
|
||||
|
||||
def _cf_simple_api_call(self, api_call, method='GET', payload=None):
|
||||
if self.api_token:
|
||||
headers = {
|
||||
'Authorization': 'Bearer ' + self.api_token,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
else:
|
||||
headers = {
|
||||
'X-Auth-Email': self.account_email,
|
||||
'X-Auth-Key': self.account_api_key,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
data = None
|
||||
if payload:
|
||||
try:
|
||||
data = json.dumps(payload)
|
||||
except Exception as e:
|
||||
self.module.fail_json(msg="Failed to encode payload as JSON: %s " % to_native(e))
|
||||
|
||||
resp, info = fetch_url(self.module,
|
||||
self.cf_api_endpoint + api_call,
|
||||
headers=headers,
|
||||
data=data,
|
||||
method=method,
|
||||
timeout=self.timeout)
|
||||
|
||||
if info['status'] not in [200, 304, 400, 401, 403, 429, 405, 415]:
|
||||
self.module.fail_json(msg="Failed API call {0}; got unexpected HTTP code {1}".format(api_call, info['status']))
|
||||
|
||||
error_msg = ''
|
||||
if info['status'] == 401:
|
||||
# Unauthorized
|
||||
error_msg = "API user does not have permission; Status: {0}; Method: {1}: Call: {2}".format(info['status'], method, api_call)
|
||||
elif info['status'] == 403:
|
||||
# Forbidden
|
||||
error_msg = "API request not authenticated; Status: {0}; Method: {1}: Call: {2}".format(info['status'], method, api_call)
|
||||
elif info['status'] == 429:
|
||||
# Too many requests
|
||||
error_msg = "API client is rate limited; Status: {0}; Method: {1}: Call: {2}".format(info['status'], method, api_call)
|
||||
elif info['status'] == 405:
|
||||
# Method not allowed
|
||||
error_msg = "API incorrect HTTP method provided; Status: {0}; Method: {1}: Call: {2}".format(info['status'], method, api_call)
|
||||
elif info['status'] == 415:
|
||||
# Unsupported Media Type
|
||||
error_msg = "API request is not valid JSON; Status: {0}; Method: {1}: Call: {2}".format(info['status'], method, api_call)
|
||||
elif info['status'] == 400:
|
||||
# Bad Request
|
||||
error_msg = "API bad request; Status: {0}; Method: {1}: Call: {2}".format(info['status'], method, api_call)
|
||||
|
||||
result = None
|
||||
try:
|
||||
content = resp.read()
|
||||
except AttributeError:
|
||||
if info['body']:
|
||||
content = info['body']
|
||||
else:
|
||||
error_msg += "; The API response was empty"
|
||||
|
||||
if content:
|
||||
try:
|
||||
result = json.loads(to_text(content, errors='surrogate_or_strict'))
|
||||
except (getattr(json, 'JSONDecodeError', ValueError)) as e:
|
||||
error_msg += "; Failed to parse API response with error {0}: {1}".format(to_native(e), content)
|
||||
|
||||
# Without a valid/parsed JSON response no more error processing can be done
|
||||
if result is None:
|
||||
self.module.fail_json(msg=error_msg)
|
||||
|
||||
if not result['success']:
|
||||
error_msg += "; Error details: "
|
||||
for error in result['errors']:
|
||||
error_msg += "code: {0}, error: {1}; ".format(error['code'], error['message'])
|
||||
if 'error_chain' in error:
|
||||
for chain_error in error['error_chain']:
|
||||
error_msg += "code: {0}, error: {1}; ".format(chain_error['code'], chain_error['message'])
|
||||
self.module.fail_json(msg=error_msg)
|
||||
|
||||
return result, info['status']
|
||||
|
||||
def _cf_api_call(self, api_call, method='GET', payload=None):
|
||||
result, status = self._cf_simple_api_call(api_call, method, payload)
|
||||
|
||||
data = result['result']
|
||||
|
||||
if 'result_info' in result:
|
||||
pagination = result['result_info']
|
||||
if pagination['total_pages'] > 1:
|
||||
next_page = int(pagination['page']) + 1
|
||||
parameters = ['page={0}'.format(next_page)]
|
||||
# strip "page" parameter from call parameters (if there are any)
|
||||
if '?' in api_call:
|
||||
raw_api_call, query = api_call.split('?', 1)
|
||||
parameters += [param for param in query.split('&') if not param.startswith('page')]
|
||||
else:
|
||||
raw_api_call = api_call
|
||||
while next_page <= pagination['total_pages']:
|
||||
raw_api_call += '?' + '&'.join(parameters)
|
||||
result, status = self._cf_simple_api_call(raw_api_call, method, payload)
|
||||
data += result['result']
|
||||
next_page += 1
|
||||
|
||||
return data, status
|
||||
|
||||
def _get_zone_id(self, zone=None):
|
||||
if not zone:
|
||||
zone = self.zone
|
||||
|
||||
zones = self.get_zones(zone)
|
||||
if len(zones) > 1:
|
||||
self.module.fail_json(msg="More than one zone matches {0}".format(zone))
|
||||
|
||||
if len(zones) < 1:
|
||||
self.module.fail_json(msg="No zone found with name {0}".format(zone))
|
||||
|
||||
return zones[0]['id']
|
||||
|
||||
def get_zones(self, name=None):
|
||||
if not name:
|
||||
name = self.zone
|
||||
param = ''
|
||||
if name:
|
||||
param = '?' + urlencode({'name': name})
|
||||
zones, status = self._cf_api_call('/zones' + param)
|
||||
return zones
|
||||
|
||||
def get_dns_records(self, zone_name=None, type=None, record=None, value=''):
|
||||
if not zone_name:
|
||||
zone_name = self.zone
|
||||
if not type:
|
||||
type = self.type
|
||||
if not record:
|
||||
record = self.record
|
||||
# necessary because None as value means to override user
|
||||
# set module value
|
||||
if (not value) and (value is not None):
|
||||
value = self.value
|
||||
|
||||
zone_id = self._get_zone_id()
|
||||
api_call = '/zones/{0}/dns_records'.format(zone_id)
|
||||
query = {}
|
||||
if type:
|
||||
query['type'] = type
|
||||
if record:
|
||||
query['name'] = record
|
||||
if value:
|
||||
query['content'] = value
|
||||
if query:
|
||||
api_call += '?' + urlencode(query)
|
||||
|
||||
records, status = self._cf_api_call(api_call)
|
||||
return records
|
||||
|
||||
def delete_dns_records(self, **kwargs):
|
||||
params = {}
|
||||
for param in ['port', 'proto', 'service', 'solo', 'type', 'record', 'value', 'weight', 'zone',
|
||||
'algorithm', 'cert_usage', 'hash_type', 'selector', 'key_tag']:
|
||||
if param in kwargs:
|
||||
params[param] = kwargs[param]
|
||||
else:
|
||||
params[param] = getattr(self, param)
|
||||
|
||||
records = []
|
||||
content = params['value']
|
||||
search_record = params['record']
|
||||
if params['type'] == 'SRV':
|
||||
if not (params['value'] is None or params['value'] == ''):
|
||||
content = str(params['weight']) + '\t' + str(params['port']) + '\t' + params['value']
|
||||
search_record = params['service'] + '.' + params['proto'] + '.' + params['record']
|
||||
elif params['type'] == 'DS':
|
||||
if not (params['value'] is None or params['value'] == ''):
|
||||
content = str(params['key_tag']) + '\t' + str(params['algorithm']) + '\t' + str(params['hash_type']) + '\t' + params['value']
|
||||
elif params['type'] == 'SSHFP':
|
||||
if not (params['value'] is None or params['value'] == ''):
|
||||
content = str(params['algorithm']) + '\t' + str(params['hash_type']) + '\t' + params['value']
|
||||
elif params['type'] == 'TLSA':
|
||||
if not (params['value'] is None or params['value'] == ''):
|
||||
content = str(params['cert_usage']) + '\t' + str(params['selector']) + '\t' + str(params['hash_type']) + '\t' + params['value']
|
||||
search_record = params['port'] + '.' + params['proto'] + '.' + params['record']
|
||||
if params['solo']:
|
||||
search_value = None
|
||||
else:
|
||||
search_value = content
|
||||
|
||||
records = self.get_dns_records(params['zone'], params['type'], search_record, search_value)
|
||||
|
||||
for rr in records:
|
||||
if params['solo']:
|
||||
if not ((rr['type'] == params['type']) and (rr['name'] == search_record) and (rr['content'] == content)):
|
||||
self.changed = True
|
||||
if not self.module.check_mode:
|
||||
result, info = self._cf_api_call('/zones/{0}/dns_records/{1}'.format(rr['zone_id'], rr['id']), 'DELETE')
|
||||
else:
|
||||
self.changed = True
|
||||
if not self.module.check_mode:
|
||||
result, info = self._cf_api_call('/zones/{0}/dns_records/{1}'.format(rr['zone_id'], rr['id']), 'DELETE')
|
||||
return self.changed
|
||||
|
||||
def ensure_dns_record(self, **kwargs):
|
||||
params = {}
|
||||
for param in ['port', 'priority', 'proto', 'proxied', 'service', 'ttl', 'type', 'record', 'value', 'weight', 'zone',
|
||||
'algorithm', 'cert_usage', 'hash_type', 'selector', 'key_tag']:
|
||||
if param in kwargs:
|
||||
params[param] = kwargs[param]
|
||||
else:
|
||||
params[param] = getattr(self, param)
|
||||
|
||||
search_value = params['value']
|
||||
search_record = params['record']
|
||||
new_record = None
|
||||
if (params['type'] is None) or (params['record'] is None):
|
||||
self.module.fail_json(msg="You must provide a type and a record to create a new record")
|
||||
|
||||
if (params['type'] in ['A', 'AAAA', 'CNAME', 'TXT', 'MX', 'NS', 'SPF']):
|
||||
if not params['value']:
|
||||
self.module.fail_json(msg="You must provide a non-empty value to create this record type")
|
||||
|
||||
# there can only be one CNAME per record
|
||||
# ignoring the value when searching for existing
|
||||
# CNAME records allows us to update the value if it
|
||||
# changes
|
||||
if params['type'] == 'CNAME':
|
||||
search_value = None
|
||||
|
||||
new_record = {
|
||||
"type": params['type'],
|
||||
"name": params['record'],
|
||||
"content": params['value'],
|
||||
"ttl": params['ttl']
|
||||
}
|
||||
|
||||
if (params['type'] in ['A', 'AAAA', 'CNAME']):
|
||||
new_record["proxied"] = params["proxied"]
|
||||
|
||||
if params['type'] == 'MX':
|
||||
for attr in [params['priority'], params['value']]:
|
||||
if (attr is None) or (attr == ''):
|
||||
self.module.fail_json(msg="You must provide priority and a value to create this record type")
|
||||
new_record = {
|
||||
"type": params['type'],
|
||||
"name": params['record'],
|
||||
"content": params['value'],
|
||||
"priority": params['priority'],
|
||||
"ttl": params['ttl']
|
||||
}
|
||||
|
||||
if params['type'] == 'SRV':
|
||||
for attr in [params['port'], params['priority'], params['proto'], params['service'], params['weight'], params['value']]:
|
||||
if (attr is None) or (attr == ''):
|
||||
self.module.fail_json(msg="You must provide port, priority, proto, service, weight and a value to create this record type")
|
||||
srv_data = {
|
||||
"target": params['value'],
|
||||
"port": params['port'],
|
||||
"weight": params['weight'],
|
||||
"priority": params['priority'],
|
||||
"name": params['record'][:-len('.' + params['zone'])],
|
||||
"proto": params['proto'],
|
||||
"service": params['service']
|
||||
}
|
||||
new_record = {"type": params['type'], "ttl": params['ttl'], 'data': srv_data}
|
||||
search_value = str(params['weight']) + '\t' + str(params['port']) + '\t' + params['value']
|
||||
search_record = params['service'] + '.' + params['proto'] + '.' + params['record']
|
||||
|
||||
if params['type'] == 'DS':
|
||||
for attr in [params['key_tag'], params['algorithm'], params['hash_type'], params['value']]:
|
||||
if (attr is None) or (attr == ''):
|
||||
self.module.fail_json(msg="You must provide key_tag, algorithm, hash_type and a value to create this record type")
|
||||
ds_data = {
|
||||
"key_tag": params['key_tag'],
|
||||
"algorithm": params['algorithm'],
|
||||
"digest_type": params['hash_type'],
|
||||
"digest": params['value'],
|
||||
}
|
||||
new_record = {
|
||||
"type": params['type'],
|
||||
"name": params['record'],
|
||||
'data': ds_data,
|
||||
"ttl": params['ttl'],
|
||||
}
|
||||
search_value = str(params['key_tag']) + '\t' + str(params['algorithm']) + '\t' + str(params['hash_type']) + '\t' + params['value']
|
||||
|
||||
if params['type'] == 'SSHFP':
|
||||
for attr in [params['algorithm'], params['hash_type'], params['value']]:
|
||||
if (attr is None) or (attr == ''):
|
||||
self.module.fail_json(msg="You must provide algorithm, hash_type and a value to create this record type")
|
||||
sshfp_data = {
|
||||
"fingerprint": params['value'],
|
||||
"type": params['hash_type'],
|
||||
"algorithm": params['algorithm'],
|
||||
}
|
||||
new_record = {
|
||||
"type": params['type'],
|
||||
"name": params['record'],
|
||||
'data': sshfp_data,
|
||||
"ttl": params['ttl'],
|
||||
}
|
||||
search_value = str(params['algorithm']) + '\t' + str(params['hash_type']) + '\t' + params['value']
|
||||
|
||||
if params['type'] == 'TLSA':
|
||||
for attr in [params['port'], params['proto'], params['cert_usage'], params['selector'], params['hash_type'], params['value']]:
|
||||
if (attr is None) or (attr == ''):
|
||||
self.module.fail_json(msg="You must provide port, proto, cert_usage, selector, hash_type and a value to create this record type")
|
||||
search_record = params['port'] + '.' + params['proto'] + '.' + params['record']
|
||||
tlsa_data = {
|
||||
"usage": params['cert_usage'],
|
||||
"selector": params['selector'],
|
||||
"matching_type": params['hash_type'],
|
||||
"certificate": params['value'],
|
||||
}
|
||||
new_record = {
|
||||
"type": params['type'],
|
||||
"name": search_record,
|
||||
'data': tlsa_data,
|
||||
"ttl": params['ttl'],
|
||||
}
|
||||
search_value = str(params['cert_usage']) + '\t' + str(params['selector']) + '\t' + str(params['hash_type']) + '\t' + params['value']
|
||||
|
||||
zone_id = self._get_zone_id(params['zone'])
|
||||
records = self.get_dns_records(params['zone'], params['type'], search_record, search_value)
|
||||
# in theory this should be impossible as cloudflare does not allow
|
||||
# the creation of duplicate records but lets cover it anyways
|
||||
if len(records) > 1:
|
||||
self.module.fail_json(msg="More than one record already exists for the given attributes. That should be impossible, please open an issue!")
|
||||
# record already exists, check if it must be updated
|
||||
if len(records) == 1:
|
||||
cur_record = records[0]
|
||||
do_update = False
|
||||
if (params['ttl'] is not None) and (cur_record['ttl'] != params['ttl']):
|
||||
do_update = True
|
||||
if (params['priority'] is not None) and ('priority' in cur_record) and (cur_record['priority'] != params['priority']):
|
||||
do_update = True
|
||||
if ('proxied' in new_record) and ('proxied' in cur_record) and (cur_record['proxied'] != params['proxied']):
|
||||
do_update = True
|
||||
if ('data' in new_record) and ('data' in cur_record):
|
||||
if (cur_record['data'] != new_record['data']):
|
||||
do_update = True
|
||||
if (params['type'] == 'CNAME') and (cur_record['content'] != new_record['content']):
|
||||
do_update = True
|
||||
if do_update:
|
||||
if self.module.check_mode:
|
||||
result = new_record
|
||||
else:
|
||||
result, info = self._cf_api_call('/zones/{0}/dns_records/{1}'.format(zone_id, records[0]['id']), 'PUT', new_record)
|
||||
self.changed = True
|
||||
return result, self.changed
|
||||
else:
|
||||
return records, self.changed
|
||||
if self.module.check_mode:
|
||||
result = new_record
|
||||
else:
|
||||
result, info = self._cf_api_call('/zones/{0}/dns_records'.format(zone_id), 'POST', new_record)
|
||||
self.changed = True
|
||||
return result, self.changed
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
api_token=dict(type='str', required=False, no_log=True),
|
||||
account_api_key=dict(type='str', required=False, no_log=True, aliases=['account_api_token']),
|
||||
account_email=dict(type='str', required=False),
|
||||
algorithm=dict(type='int'),
|
||||
cert_usage=dict(type='int', choices=[0, 1, 2, 3]),
|
||||
hash_type=dict(type='int', choices=[1, 2]),
|
||||
key_tag=dict(type='int'),
|
||||
port=dict(type='int'),
|
||||
priority=dict(type='int', default=1),
|
||||
proto=dict(type='str'),
|
||||
proxied=dict(type='bool', default=False),
|
||||
record=dict(type='str', default='@', aliases=['name']),
|
||||
selector=dict(type='int', choices=[0, 1]),
|
||||
service=dict(type='str'),
|
||||
solo=dict(type='bool'),
|
||||
state=dict(type='str', default='present', choices=['absent', 'present']),
|
||||
timeout=dict(type='int', default=30),
|
||||
ttl=dict(type='int', default=1),
|
||||
type=dict(type='str', choices=['A', 'AAAA', 'CNAME', 'DS', 'MX', 'NS', 'SPF', 'SRV', 'SSHFP', 'TLSA', 'TXT']),
|
||||
value=dict(type='str', aliases=['content']),
|
||||
weight=dict(type='int', default=1),
|
||||
zone=dict(type='str', required=True, aliases=['domain']),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
required_if=[
|
||||
('state', 'present', ['record', 'type', 'value']),
|
||||
('state', 'absent', ['record']),
|
||||
('type', 'SRV', ['proto', 'service']),
|
||||
('type', 'TLSA', ['proto', 'port']),
|
||||
],
|
||||
)
|
||||
|
||||
if not module.params['api_token'] and not (module.params['account_api_key'] and module.params['account_email']):
|
||||
module.fail_json(msg="Either api_token or account_api_key and account_email params are required.")
|
||||
if module.params['type'] == 'SRV':
|
||||
if not ((module.params['weight'] is not None and module.params['port'] is not None
|
||||
and not (module.params['value'] is None or module.params['value'] == ''))
|
||||
or (module.params['weight'] is None and module.params['port'] is None
|
||||
and (module.params['value'] is None or module.params['value'] == ''))):
|
||||
module.fail_json(msg="For SRV records the params weight, port and value all need to be defined, or not at all.")
|
||||
|
||||
if module.params['type'] == 'SSHFP':
|
||||
if not ((module.params['algorithm'] is not None and module.params['hash_type'] is not None
|
||||
and not (module.params['value'] is None or module.params['value'] == ''))
|
||||
or (module.params['algorithm'] is None and module.params['hash_type'] is None
|
||||
and (module.params['value'] is None or module.params['value'] == ''))):
|
||||
module.fail_json(msg="For SSHFP records the params algorithm, hash_type and value all need to be defined, or not at all.")
|
||||
|
||||
if module.params['type'] == 'TLSA':
|
||||
if not ((module.params['cert_usage'] is not None and module.params['selector'] is not None and module.params['hash_type'] is not None
|
||||
and not (module.params['value'] is None or module.params['value'] == ''))
|
||||
or (module.params['cert_usage'] is None and module.params['selector'] is None and module.params['hash_type'] is None
|
||||
and (module.params['value'] is None or module.params['value'] == ''))):
|
||||
module.fail_json(msg="For TLSA records the params cert_usage, selector, hash_type and value all need to be defined, or not at all.")
|
||||
|
||||
if module.params['type'] == 'DS':
|
||||
if not ((module.params['key_tag'] is not None and module.params['algorithm'] is not None and module.params['hash_type'] is not None
|
||||
and not (module.params['value'] is None or module.params['value'] == ''))
|
||||
or (module.params['key_tag'] is None and module.params['algorithm'] is None and module.params['hash_type'] is None
|
||||
and (module.params['value'] is None or module.params['value'] == ''))):
|
||||
module.fail_json(msg="For DS records the params key_tag, algorithm, hash_type and value all need to be defined, or not at all.")
|
||||
|
||||
changed = False
|
||||
cf_api = CloudflareAPI(module)
|
||||
|
||||
# sanity checks
|
||||
if cf_api.is_solo and cf_api.state == 'absent':
|
||||
module.fail_json(msg="solo=true can only be used with state=present")
|
||||
|
||||
# perform add, delete or update (only the TTL can be updated) of one or
|
||||
# more records
|
||||
if cf_api.state == 'present':
|
||||
# delete all records matching record name + type
|
||||
if cf_api.is_solo:
|
||||
changed = cf_api.delete_dns_records(solo=cf_api.is_solo)
|
||||
result, changed = cf_api.ensure_dns_record()
|
||||
if isinstance(result, list):
|
||||
module.exit_json(changed=changed, result={'record': result[0]})
|
||||
|
||||
module.exit_json(changed=changed, result={'record': result})
|
||||
else:
|
||||
# force solo to False, just to be sure
|
||||
changed = cf_api.delete_dns_records(solo=False)
|
||||
module.exit_json(changed=changed)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
343
plugins/modules/net_tools/dnsimple.py
Normal file
343
plugins/modules/net_tools/dnsimple.py
Normal file
@@ -0,0 +1,343 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# Copyright: Ansible Project
|
||||
#
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {
|
||||
'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'
|
||||
}
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: dnsimple
|
||||
short_description: Interface with dnsimple.com (a DNS hosting service)
|
||||
description:
|
||||
- "Manages domains and records via the DNSimple API, see the docs: U(http://developer.dnsimple.com/)."
|
||||
notes:
|
||||
- DNSimple API v1 is deprecated. Please install dnsimple-python>=1.0.0 which uses v2 API.
|
||||
options:
|
||||
account_email:
|
||||
description:
|
||||
- Account email. If omitted, the environment variables C(DNSIMPLE_EMAIL) and C(DNSIMPLE_API_TOKEN) will be looked for.
|
||||
- "If those aren't found, a C(.dnsimple) file will be looked for, see: U(https://github.com/mikemaccana/dnsimple-python#getting-started)."
|
||||
type: str
|
||||
account_api_token:
|
||||
description:
|
||||
- Account API token. See I(account_email) for more information.
|
||||
type: str
|
||||
domain:
|
||||
description:
|
||||
- Domain to work with. Can be the domain name (e.g. "mydomain.com") or the numeric ID of the domain in DNSimple.
|
||||
- If omitted, a list of domains will be returned.
|
||||
- If domain is present but the domain doesn't exist, it will be created.
|
||||
type: str
|
||||
record:
|
||||
description:
|
||||
- Record to add, if blank a record for the domain will be created, supports the wildcard (*).
|
||||
type: str
|
||||
record_ids:
|
||||
description:
|
||||
- List of records to ensure they either exist or do not exist.
|
||||
type: list
|
||||
type:
|
||||
description:
|
||||
- The type of DNS record to create.
|
||||
choices: [ 'A', 'ALIAS', 'CNAME', 'MX', 'SPF', 'URL', 'TXT', 'NS', 'SRV', 'NAPTR', 'PTR', 'AAAA', 'SSHFP', 'HINFO', 'POOL' ]
|
||||
type: str
|
||||
ttl:
|
||||
description:
|
||||
- The TTL to give the new record in seconds.
|
||||
default: 3600
|
||||
type: int
|
||||
value:
|
||||
description:
|
||||
- Record value.
|
||||
- Must be specified when trying to ensure a record exists.
|
||||
type: str
|
||||
priority:
|
||||
description:
|
||||
- Record priority.
|
||||
type: int
|
||||
state:
|
||||
description:
|
||||
- whether the record should exist or not.
|
||||
choices: [ 'present', 'absent' ]
|
||||
default: present
|
||||
type: str
|
||||
solo:
|
||||
description:
|
||||
- Whether the record should be the only one for that record type and record name.
|
||||
- Only use with C(state) is set to C(present) on a record.
|
||||
type: 'bool'
|
||||
default: no
|
||||
requirements:
|
||||
- "dnsimple >= 1.0.0"
|
||||
author: "Alex Coomans (@drcapulet)"
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Authenticate using email and API token and fetch all domains
|
||||
dnsimple:
|
||||
account_email: test@example.com
|
||||
account_api_token: dummyapitoken
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Fetch my.com domain records
|
||||
dnsimple:
|
||||
domain: my.com
|
||||
state: present
|
||||
delegate_to: localhost
|
||||
register: records
|
||||
|
||||
- name: Delete a domain
|
||||
dnsimple:
|
||||
domain: my.com
|
||||
state: absent
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Create a test.my.com A record to point to 127.0.0.1
|
||||
dnsimple:
|
||||
domain: my.com
|
||||
record: test
|
||||
type: A
|
||||
value: 127.0.0.1
|
||||
delegate_to: localhost
|
||||
register: record
|
||||
|
||||
- name: Delete record using record_ids
|
||||
dnsimple:
|
||||
domain: my.com
|
||||
record_ids: '{{ record["id"] }}'
|
||||
state: absent
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Create a my.com CNAME record to example.com
|
||||
dnsimple:
|
||||
domain: my.com
|
||||
record: ''
|
||||
type: CNAME
|
||||
value: example.com
|
||||
state: present
|
||||
delegate_to: localhost
|
||||
|
||||
- name: change TTL value for a record
|
||||
dnsimple:
|
||||
domain: my.com
|
||||
record: ''
|
||||
type: CNAME
|
||||
value: example.com
|
||||
ttl: 600
|
||||
state: present
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Delete the record
|
||||
dnsimple:
|
||||
domain: my.com
|
||||
record: ''
|
||||
type: CNAME
|
||||
value: example.com
|
||||
state: absent
|
||||
delegate_to: localhost
|
||||
'''
|
||||
|
||||
RETURN = r"""# """
|
||||
|
||||
import os
|
||||
import traceback
|
||||
from distutils.version import LooseVersion
|
||||
|
||||
DNSIMPLE_IMP_ERR = None
|
||||
try:
|
||||
from dnsimple import DNSimple
|
||||
from dnsimple.dnsimple import __version__ as dnsimple_version
|
||||
from dnsimple.dnsimple import DNSimpleException
|
||||
HAS_DNSIMPLE = True
|
||||
except ImportError:
|
||||
DNSIMPLE_IMP_ERR = traceback.format_exc()
|
||||
HAS_DNSIMPLE = False
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
account_email=dict(type='str'),
|
||||
account_api_token=dict(type='str', no_log=True),
|
||||
domain=dict(type='str'),
|
||||
record=dict(type='str'),
|
||||
record_ids=dict(type='list'),
|
||||
type=dict(type='str', choices=['A', 'ALIAS', 'CNAME', 'MX', 'SPF', 'URL', 'TXT', 'NS', 'SRV', 'NAPTR', 'PTR', 'AAAA', 'SSHFP', 'HINFO',
|
||||
'POOL']),
|
||||
ttl=dict(type='int', default=3600),
|
||||
value=dict(type='str'),
|
||||
priority=dict(type='int'),
|
||||
state=dict(type='str', choices=['present', 'absent'], default='present'),
|
||||
solo=dict(type='bool', default=False),
|
||||
),
|
||||
required_together=[
|
||||
['record', 'value']
|
||||
],
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
if not HAS_DNSIMPLE:
|
||||
module.fail_json(msg=missing_required_lib('dnsimple'), exception=DNSIMPLE_IMP_ERR)
|
||||
|
||||
if LooseVersion(dnsimple_version) < LooseVersion('1.0.0'):
|
||||
module.fail_json(msg="Current version of dnsimple Python module [%s] uses 'v1' API which is deprecated."
|
||||
" Please upgrade to version 1.0.0 and above to use dnsimple 'v2' API." % dnsimple_version)
|
||||
|
||||
account_email = module.params.get('account_email')
|
||||
account_api_token = module.params.get('account_api_token')
|
||||
domain = module.params.get('domain')
|
||||
record = module.params.get('record')
|
||||
record_ids = module.params.get('record_ids')
|
||||
record_type = module.params.get('type')
|
||||
ttl = module.params.get('ttl')
|
||||
value = module.params.get('value')
|
||||
priority = module.params.get('priority')
|
||||
state = module.params.get('state')
|
||||
is_solo = module.params.get('solo')
|
||||
|
||||
if account_email and account_api_token:
|
||||
client = DNSimple(email=account_email, api_token=account_api_token)
|
||||
elif os.environ.get('DNSIMPLE_EMAIL') and os.environ.get('DNSIMPLE_API_TOKEN'):
|
||||
client = DNSimple(email=os.environ.get('DNSIMPLE_EMAIL'), api_token=os.environ.get('DNSIMPLE_API_TOKEN'))
|
||||
else:
|
||||
client = DNSimple()
|
||||
|
||||
try:
|
||||
# Let's figure out what operation we want to do
|
||||
|
||||
# No domain, return a list
|
||||
if not domain:
|
||||
domains = client.domains()
|
||||
module.exit_json(changed=False, result=[d['domain'] for d in domains])
|
||||
|
||||
# Domain & No record
|
||||
if domain and record is None and not record_ids:
|
||||
domains = [d['domain'] for d in client.domains()]
|
||||
if domain.isdigit():
|
||||
dr = next((d for d in domains if d['id'] == int(domain)), None)
|
||||
else:
|
||||
dr = next((d for d in domains if d['name'] == domain), None)
|
||||
if state == 'present':
|
||||
if dr:
|
||||
module.exit_json(changed=False, result=dr)
|
||||
else:
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
else:
|
||||
module.exit_json(changed=True, result=client.add_domain(domain)['domain'])
|
||||
|
||||
# state is absent
|
||||
else:
|
||||
if dr:
|
||||
if not module.check_mode:
|
||||
client.delete(domain)
|
||||
module.exit_json(changed=True)
|
||||
else:
|
||||
module.exit_json(changed=False)
|
||||
|
||||
# need the not none check since record could be an empty string
|
||||
if domain and record is not None:
|
||||
records = [r['record'] for r in client.records(str(domain), params={'name': record})]
|
||||
|
||||
if not record_type:
|
||||
module.fail_json(msg="Missing the record type")
|
||||
|
||||
if not value:
|
||||
module.fail_json(msg="Missing the record value")
|
||||
|
||||
rr = next((r for r in records if r['name'] == record and r['type'] == record_type and r['content'] == value), None)
|
||||
|
||||
if state == 'present':
|
||||
changed = False
|
||||
if is_solo:
|
||||
# delete any records that have the same name and record type
|
||||
same_type = [r['id'] for r in records if r['name'] == record and r['type'] == record_type]
|
||||
if rr:
|
||||
same_type = [rid for rid in same_type if rid != rr['id']]
|
||||
if same_type:
|
||||
if not module.check_mode:
|
||||
for rid in same_type:
|
||||
client.delete_record(str(domain), rid)
|
||||
changed = True
|
||||
if rr:
|
||||
# check if we need to update
|
||||
if rr['ttl'] != ttl or rr['priority'] != priority:
|
||||
data = {}
|
||||
if ttl:
|
||||
data['ttl'] = ttl
|
||||
if priority:
|
||||
data['priority'] = priority
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
else:
|
||||
module.exit_json(changed=True, result=client.update_record(str(domain), str(rr['id']), data)['record'])
|
||||
else:
|
||||
module.exit_json(changed=changed, result=rr)
|
||||
else:
|
||||
# create it
|
||||
data = {
|
||||
'name': record,
|
||||
'type': record_type,
|
||||
'content': value,
|
||||
}
|
||||
if ttl:
|
||||
data['ttl'] = ttl
|
||||
if priority:
|
||||
data['priority'] = priority
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
else:
|
||||
module.exit_json(changed=True, result=client.add_record(str(domain), data)['record'])
|
||||
|
||||
# state is absent
|
||||
else:
|
||||
if rr:
|
||||
if not module.check_mode:
|
||||
client.delete_record(str(domain), rr['id'])
|
||||
module.exit_json(changed=True)
|
||||
else:
|
||||
module.exit_json(changed=False)
|
||||
|
||||
# Make sure these record_ids either all exist or none
|
||||
if domain and record_ids:
|
||||
current_records = [str(r['record']['id']) for r in client.records(str(domain))]
|
||||
wanted_records = [str(r) for r in record_ids]
|
||||
if state == 'present':
|
||||
difference = list(set(wanted_records) - set(current_records))
|
||||
if difference:
|
||||
module.fail_json(msg="Missing the following records: %s" % difference)
|
||||
else:
|
||||
module.exit_json(changed=False)
|
||||
|
||||
# state is absent
|
||||
else:
|
||||
difference = list(set(wanted_records) & set(current_records))
|
||||
if difference:
|
||||
if not module.check_mode:
|
||||
for rid in difference:
|
||||
client.delete_record(str(domain), rid)
|
||||
module.exit_json(changed=True)
|
||||
else:
|
||||
module.exit_json(changed=False)
|
||||
|
||||
except DNSimpleException as e:
|
||||
module.fail_json(msg="Unable to contact DNSimple: %s" % e.message)
|
||||
|
||||
module.fail_json(msg="Unknown what you wanted me to do")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
697
plugins/modules/net_tools/dnsmadeeasy.py
Normal file
697
plugins/modules/net_tools/dnsmadeeasy.py
Normal file
@@ -0,0 +1,697 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: dnsmadeeasy
|
||||
short_description: Interface with dnsmadeeasy.com (a DNS hosting service).
|
||||
description:
|
||||
- >
|
||||
Manages DNS records via the v2 REST API of the DNS Made Easy service. It handles records only; there is no manipulation of domains or
|
||||
monitor/account support yet. See: U(https://www.dnsmadeeasy.com/integration/restapi/)
|
||||
options:
|
||||
account_key:
|
||||
description:
|
||||
- Account API Key.
|
||||
required: true
|
||||
|
||||
account_secret:
|
||||
description:
|
||||
- Account Secret Key.
|
||||
required: true
|
||||
|
||||
domain:
|
||||
description:
|
||||
- Domain to work with. Can be the domain name (e.g. "mydomain.com") or the numeric ID of the domain in DNS Made Easy (e.g. "839989") for faster
|
||||
resolution
|
||||
required: true
|
||||
|
||||
sandbox:
|
||||
description:
|
||||
- Decides if the sandbox API should be used. Otherwise (default) the production API of DNS Made Easy is used.
|
||||
type: bool
|
||||
default: 'no'
|
||||
|
||||
record_name:
|
||||
description:
|
||||
- Record name to get/create/delete/update. If record_name is not specified; all records for the domain will be returned in "result" regardless
|
||||
of the state argument.
|
||||
|
||||
record_type:
|
||||
description:
|
||||
- Record type.
|
||||
choices: [ 'A', 'AAAA', 'CNAME', 'ANAME', 'HTTPRED', 'MX', 'NS', 'PTR', 'SRV', 'TXT' ]
|
||||
|
||||
record_value:
|
||||
description:
|
||||
- >
|
||||
Record value. HTTPRED: <redirection URL>, MX: <priority> <target name>, NS: <name server>, PTR: <target name>,
|
||||
SRV: <priority> <weight> <port> <target name>, TXT: <text value>"
|
||||
- >
|
||||
If record_value is not specified; no changes will be made and the record will be returned in 'result'
|
||||
(in other words, this module can be used to fetch a record's current id, type, and ttl)
|
||||
|
||||
record_ttl:
|
||||
description:
|
||||
- record's "Time to live". Number of seconds the record remains cached in DNS servers.
|
||||
default: 1800
|
||||
|
||||
state:
|
||||
description:
|
||||
- whether the record should exist or not
|
||||
required: true
|
||||
choices: [ 'present', 'absent' ]
|
||||
|
||||
validate_certs:
|
||||
description:
|
||||
- If C(no), SSL certificates will not be validated. This should only be used
|
||||
on personally controlled sites using self-signed certificates.
|
||||
type: bool
|
||||
default: 'yes'
|
||||
|
||||
monitor:
|
||||
description:
|
||||
- If C(yes), add or change the monitor. This is applicable only for A records.
|
||||
type: bool
|
||||
default: 'no'
|
||||
|
||||
systemDescription:
|
||||
description:
|
||||
- Description used by the monitor.
|
||||
required: true
|
||||
default: ''
|
||||
|
||||
maxEmails:
|
||||
description:
|
||||
- Number of emails sent to the contact list by the monitor.
|
||||
required: true
|
||||
default: 1
|
||||
|
||||
protocol:
|
||||
description:
|
||||
- Protocol used by the monitor.
|
||||
required: true
|
||||
default: 'HTTP'
|
||||
choices: ['TCP', 'UDP', 'HTTP', 'DNS', 'SMTP', 'HTTPS']
|
||||
|
||||
port:
|
||||
description:
|
||||
- Port used by the monitor.
|
||||
required: true
|
||||
default: 80
|
||||
|
||||
sensitivity:
|
||||
description:
|
||||
- Number of checks the monitor performs before a failover occurs where Low = 8, Medium = 5,and High = 3.
|
||||
required: true
|
||||
default: 'Medium'
|
||||
choices: ['Low', 'Medium', 'High']
|
||||
|
||||
contactList:
|
||||
description:
|
||||
- Name or id of the contact list that the monitor will notify.
|
||||
- The default C('') means the Account Owner.
|
||||
required: true
|
||||
default: ''
|
||||
|
||||
httpFqdn:
|
||||
description:
|
||||
- The fully qualified domain name used by the monitor.
|
||||
|
||||
httpFile:
|
||||
description:
|
||||
- The file at the Fqdn that the monitor queries for HTTP or HTTPS.
|
||||
|
||||
httpQueryString:
|
||||
description:
|
||||
- The string in the httpFile that the monitor queries for HTTP or HTTPS.
|
||||
|
||||
failover:
|
||||
description:
|
||||
- If C(yes), add or change the failover. This is applicable only for A records.
|
||||
type: bool
|
||||
default: 'no'
|
||||
|
||||
autoFailover:
|
||||
description:
|
||||
- If true, fallback to the primary IP address is manual after a failover.
|
||||
- If false, fallback to the primary IP address is automatic after a failover.
|
||||
type: bool
|
||||
default: 'no'
|
||||
|
||||
ip1:
|
||||
description:
|
||||
- Primary IP address for the failover.
|
||||
- Required if adding or changing the monitor or failover.
|
||||
|
||||
ip2:
|
||||
description:
|
||||
- Secondary IP address for the failover.
|
||||
- Required if adding or changing the failover.
|
||||
|
||||
ip3:
|
||||
description:
|
||||
- Tertiary IP address for the failover.
|
||||
|
||||
ip4:
|
||||
description:
|
||||
- Quaternary IP address for the failover.
|
||||
|
||||
ip5:
|
||||
description:
|
||||
- Quinary IP address for the failover.
|
||||
|
||||
notes:
|
||||
- The DNS Made Easy service requires that machines interacting with the API have the proper time and timezone set. Be sure you are within a few
|
||||
seconds of actual time by using NTP.
|
||||
- This module returns record(s) and monitor(s) in the "result" element when 'state' is set to 'present'.
|
||||
These values can be be registered and used in your playbooks.
|
||||
- Only A records can have a monitor or failover.
|
||||
- To add failover, the 'failover', 'autoFailover', 'port', 'protocol', 'ip1', and 'ip2' options are required.
|
||||
- To add monitor, the 'monitor', 'port', 'protocol', 'maxEmails', 'systemDescription', and 'ip1' options are required.
|
||||
- The monitor and the failover will share 'port', 'protocol', and 'ip1' options.
|
||||
|
||||
requirements: [ hashlib, hmac ]
|
||||
author: "Brice Burgess (@briceburg)"
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# fetch my.com domain records
|
||||
- dnsmadeeasy:
|
||||
account_key: key
|
||||
account_secret: secret
|
||||
domain: my.com
|
||||
state: present
|
||||
register: response
|
||||
|
||||
# create / ensure the presence of a record
|
||||
- dnsmadeeasy:
|
||||
account_key: key
|
||||
account_secret: secret
|
||||
domain: my.com
|
||||
state: present
|
||||
record_name: test
|
||||
record_type: A
|
||||
record_value: 127.0.0.1
|
||||
|
||||
# update the previously created record
|
||||
- dnsmadeeasy:
|
||||
account_key: key
|
||||
account_secret: secret
|
||||
domain: my.com
|
||||
state: present
|
||||
record_name: test
|
||||
record_value: 192.0.2.23
|
||||
|
||||
# fetch a specific record
|
||||
- dnsmadeeasy:
|
||||
account_key: key
|
||||
account_secret: secret
|
||||
domain: my.com
|
||||
state: present
|
||||
record_name: test
|
||||
register: response
|
||||
|
||||
# delete a record / ensure it is absent
|
||||
- dnsmadeeasy:
|
||||
account_key: key
|
||||
account_secret: secret
|
||||
domain: my.com
|
||||
record_type: A
|
||||
state: absent
|
||||
record_name: test
|
||||
|
||||
# Add a failover
|
||||
- dnsmadeeasy:
|
||||
account_key: key
|
||||
account_secret: secret
|
||||
domain: my.com
|
||||
state: present
|
||||
record_name: test
|
||||
record_type: A
|
||||
record_value: 127.0.0.1
|
||||
failover: True
|
||||
ip1: 127.0.0.2
|
||||
ip2: 127.0.0.3
|
||||
|
||||
- dnsmadeeasy:
|
||||
account_key: key
|
||||
account_secret: secret
|
||||
domain: my.com
|
||||
state: present
|
||||
record_name: test
|
||||
record_type: A
|
||||
record_value: 127.0.0.1
|
||||
failover: True
|
||||
ip1: 127.0.0.2
|
||||
ip2: 127.0.0.3
|
||||
ip3: 127.0.0.4
|
||||
ip4: 127.0.0.5
|
||||
ip5: 127.0.0.6
|
||||
|
||||
# Add a monitor
|
||||
- dnsmadeeasy:
|
||||
account_key: key
|
||||
account_secret: secret
|
||||
domain: my.com
|
||||
state: present
|
||||
record_name: test
|
||||
record_type: A
|
||||
record_value: 127.0.0.1
|
||||
monitor: yes
|
||||
ip1: 127.0.0.2
|
||||
protocol: HTTP # default
|
||||
port: 80 # default
|
||||
maxEmails: 1
|
||||
systemDescription: Monitor Test A record
|
||||
contactList: my contact list
|
||||
|
||||
# Add a monitor with http options
|
||||
- dnsmadeeasy:
|
||||
account_key: key
|
||||
account_secret: secret
|
||||
domain: my.com
|
||||
state: present
|
||||
record_name: test
|
||||
record_type: A
|
||||
record_value: 127.0.0.1
|
||||
monitor: yes
|
||||
ip1: 127.0.0.2
|
||||
protocol: HTTP # default
|
||||
port: 80 # default
|
||||
maxEmails: 1
|
||||
systemDescription: Monitor Test A record
|
||||
contactList: 1174 # contact list id
|
||||
httpFqdn: http://my.com
|
||||
httpFile: example
|
||||
httpQueryString: some string
|
||||
|
||||
# Add a monitor and a failover
|
||||
- dnsmadeeasy:
|
||||
account_key: key
|
||||
account_secret: secret
|
||||
domain: my.com
|
||||
state: present
|
||||
record_name: test
|
||||
record_type: A
|
||||
record_value: 127.0.0.1
|
||||
failover: True
|
||||
ip1: 127.0.0.2
|
||||
ip2: 127.0.0.3
|
||||
monitor: yes
|
||||
protocol: HTTPS
|
||||
port: 443
|
||||
maxEmails: 1
|
||||
systemDescription: monitoring my.com status
|
||||
contactList: emergencycontacts
|
||||
|
||||
# Remove a failover
|
||||
- dnsmadeeasy:
|
||||
account_key: key
|
||||
account_secret: secret
|
||||
domain: my.com
|
||||
state: present
|
||||
record_name: test
|
||||
record_type: A
|
||||
record_value: 127.0.0.1
|
||||
failover: no
|
||||
|
||||
# Remove a monitor
|
||||
- dnsmadeeasy:
|
||||
account_key: key
|
||||
account_secret: secret
|
||||
domain: my.com
|
||||
state: present
|
||||
record_name: test
|
||||
record_type: A
|
||||
record_value: 127.0.0.1
|
||||
monitor: no
|
||||
'''
|
||||
|
||||
# ============================================
|
||||
# DNSMadeEasy module specific support methods.
|
||||
#
|
||||
|
||||
import json
|
||||
import hashlib
|
||||
import hmac
|
||||
import locale
|
||||
from time import strftime, gmtime
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.urls import fetch_url
|
||||
from ansible.module_utils.six.moves.urllib.parse import urlencode
|
||||
from ansible.module_utils.six import string_types
|
||||
|
||||
|
||||
class DME2(object):
|
||||
|
||||
def __init__(self, apikey, secret, domain, sandbox, module):
|
||||
self.module = module
|
||||
|
||||
self.api = apikey
|
||||
self.secret = secret
|
||||
|
||||
if sandbox:
|
||||
self.baseurl = 'https://api.sandbox.dnsmadeeasy.com/V2.0/'
|
||||
self.module.warn(warning="Sandbox is enabled. All actions are made against the URL %s" % self.baseurl)
|
||||
else:
|
||||
self.baseurl = 'https://api.dnsmadeeasy.com/V2.0/'
|
||||
|
||||
self.domain = str(domain)
|
||||
self.domain_map = None # ["domain_name"] => ID
|
||||
self.record_map = None # ["record_name"] => ID
|
||||
self.records = None # ["record_ID"] => <record>
|
||||
self.all_records = None
|
||||
self.contactList_map = None # ["contactList_name"] => ID
|
||||
|
||||
# Lookup the domain ID if passed as a domain name vs. ID
|
||||
if not self.domain.isdigit():
|
||||
self.domain = self.getDomainByName(self.domain)['id']
|
||||
|
||||
self.record_url = 'dns/managed/' + str(self.domain) + '/records'
|
||||
self.monitor_url = 'monitor'
|
||||
self.contactList_url = 'contactList'
|
||||
|
||||
def _headers(self):
|
||||
currTime = self._get_date()
|
||||
hashstring = self._create_hash(currTime)
|
||||
headers = {'x-dnsme-apiKey': self.api,
|
||||
'x-dnsme-hmac': hashstring,
|
||||
'x-dnsme-requestDate': currTime,
|
||||
'content-type': 'application/json'}
|
||||
return headers
|
||||
|
||||
def _get_date(self):
|
||||
locale.setlocale(locale.LC_TIME, 'C')
|
||||
return strftime("%a, %d %b %Y %H:%M:%S GMT", gmtime())
|
||||
|
||||
def _create_hash(self, rightnow):
|
||||
return hmac.new(self.secret.encode(), rightnow.encode(), hashlib.sha1).hexdigest()
|
||||
|
||||
def query(self, resource, method, data=None):
|
||||
url = self.baseurl + resource
|
||||
if data and not isinstance(data, string_types):
|
||||
data = urlencode(data)
|
||||
|
||||
response, info = fetch_url(self.module, url, data=data, method=method, headers=self._headers())
|
||||
if info['status'] not in (200, 201, 204):
|
||||
self.module.fail_json(msg="%s returned %s, with body: %s" % (url, info['status'], info['msg']))
|
||||
|
||||
try:
|
||||
return json.load(response)
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
def getDomain(self, domain_id):
|
||||
if not self.domain_map:
|
||||
self._instMap('domain')
|
||||
|
||||
return self.domains.get(domain_id, False)
|
||||
|
||||
def getDomainByName(self, domain_name):
|
||||
if not self.domain_map:
|
||||
self._instMap('domain')
|
||||
|
||||
return self.getDomain(self.domain_map.get(domain_name, 0))
|
||||
|
||||
def getDomains(self):
|
||||
return self.query('dns/managed', 'GET')['data']
|
||||
|
||||
def getRecord(self, record_id):
|
||||
if not self.record_map:
|
||||
self._instMap('record')
|
||||
|
||||
return self.records.get(record_id, False)
|
||||
|
||||
# Try to find a single record matching this one.
|
||||
# How we do this depends on the type of record. For instance, there
|
||||
# can be several MX records for a single record_name while there can
|
||||
# only be a single CNAME for a particular record_name. Note also that
|
||||
# there can be several records with different types for a single name.
|
||||
def getMatchingRecord(self, record_name, record_type, record_value):
|
||||
# Get all the records if not already cached
|
||||
if not self.all_records:
|
||||
self.all_records = self.getRecords()
|
||||
|
||||
if record_type in ["CNAME", "ANAME", "HTTPRED", "PTR"]:
|
||||
for result in self.all_records:
|
||||
if result['name'] == record_name and result['type'] == record_type:
|
||||
return result
|
||||
return False
|
||||
elif record_type in ["A", "AAAA", "MX", "NS", "TXT", "SRV"]:
|
||||
for result in self.all_records:
|
||||
if record_type == "MX":
|
||||
value = record_value.split(" ")[1]
|
||||
elif record_type == "SRV":
|
||||
value = record_value.split(" ")[3]
|
||||
else:
|
||||
value = record_value
|
||||
if result['name'] == record_name and result['type'] == record_type and result['value'] == value:
|
||||
return result
|
||||
return False
|
||||
else:
|
||||
raise Exception('record_type not yet supported')
|
||||
|
||||
def getRecords(self):
|
||||
return self.query(self.record_url, 'GET')['data']
|
||||
|
||||
def _instMap(self, type):
|
||||
# @TODO cache this call so it's executed only once per ansible execution
|
||||
map = {}
|
||||
results = {}
|
||||
|
||||
# iterate over e.g. self.getDomains() || self.getRecords()
|
||||
for result in getattr(self, 'get' + type.title() + 's')():
|
||||
|
||||
map[result['name']] = result['id']
|
||||
results[result['id']] = result
|
||||
|
||||
# e.g. self.domain_map || self.record_map
|
||||
setattr(self, type + '_map', map)
|
||||
setattr(self, type + 's', results) # e.g. self.domains || self.records
|
||||
|
||||
def prepareRecord(self, data):
|
||||
return json.dumps(data, separators=(',', ':'))
|
||||
|
||||
def createRecord(self, data):
|
||||
# @TODO update the cache w/ resultant record + id when impleneted
|
||||
return self.query(self.record_url, 'POST', data)
|
||||
|
||||
def updateRecord(self, record_id, data):
|
||||
# @TODO update the cache w/ resultant record + id when impleneted
|
||||
return self.query(self.record_url + '/' + str(record_id), 'PUT', data)
|
||||
|
||||
def deleteRecord(self, record_id):
|
||||
# @TODO remove record from the cache when impleneted
|
||||
return self.query(self.record_url + '/' + str(record_id), 'DELETE')
|
||||
|
||||
def getMonitor(self, record_id):
|
||||
return self.query(self.monitor_url + '/' + str(record_id), 'GET')
|
||||
|
||||
def updateMonitor(self, record_id, data):
|
||||
return self.query(self.monitor_url + '/' + str(record_id), 'PUT', data)
|
||||
|
||||
def prepareMonitor(self, data):
|
||||
return json.dumps(data, separators=(',', ':'))
|
||||
|
||||
def getContactList(self, contact_list_id):
|
||||
if not self.contactList_map:
|
||||
self._instMap('contactList')
|
||||
|
||||
return self.contactLists.get(contact_list_id, False)
|
||||
|
||||
def getContactlists(self):
|
||||
return self.query(self.contactList_url, 'GET')['data']
|
||||
|
||||
def getContactListByName(self, name):
|
||||
if not self.contactList_map:
|
||||
self._instMap('contactList')
|
||||
|
||||
return self.getContactList(self.contactList_map.get(name, 0))
|
||||
|
||||
# ===========================================
|
||||
# Module execution.
|
||||
#
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
account_key=dict(required=True),
|
||||
account_secret=dict(required=True, no_log=True),
|
||||
domain=dict(required=True),
|
||||
sandbox=dict(default='no', type='bool'),
|
||||
state=dict(required=True, choices=['present', 'absent']),
|
||||
record_name=dict(required=False),
|
||||
record_type=dict(required=False, choices=[
|
||||
'A', 'AAAA', 'CNAME', 'ANAME', 'HTTPRED', 'MX', 'NS', 'PTR', 'SRV', 'TXT']),
|
||||
record_value=dict(required=False),
|
||||
record_ttl=dict(required=False, default=1800, type='int'),
|
||||
monitor=dict(default='no', type='bool'),
|
||||
systemDescription=dict(default=''),
|
||||
maxEmails=dict(default=1, type='int'),
|
||||
protocol=dict(default='HTTP', choices=['TCP', 'UDP', 'HTTP', 'DNS', 'SMTP', 'HTTPS']),
|
||||
port=dict(default=80, type='int'),
|
||||
sensitivity=dict(default='Medium', choices=['Low', 'Medium', 'High']),
|
||||
contactList=dict(default=None),
|
||||
httpFqdn=dict(required=False),
|
||||
httpFile=dict(required=False),
|
||||
httpQueryString=dict(required=False),
|
||||
failover=dict(default='no', type='bool'),
|
||||
autoFailover=dict(default='no', type='bool'),
|
||||
ip1=dict(required=False),
|
||||
ip2=dict(required=False),
|
||||
ip3=dict(required=False),
|
||||
ip4=dict(required=False),
|
||||
ip5=dict(required=False),
|
||||
validate_certs=dict(default='yes', type='bool'),
|
||||
),
|
||||
required_together=[
|
||||
['record_value', 'record_ttl', 'record_type']
|
||||
],
|
||||
required_if=[
|
||||
['failover', True, ['autoFailover', 'port', 'protocol', 'ip1', 'ip2']],
|
||||
['monitor', True, ['port', 'protocol', 'maxEmails', 'systemDescription', 'ip1']]
|
||||
]
|
||||
)
|
||||
|
||||
protocols = dict(TCP=1, UDP=2, HTTP=3, DNS=4, SMTP=5, HTTPS=6)
|
||||
sensitivities = dict(Low=8, Medium=5, High=3)
|
||||
|
||||
DME = DME2(module.params["account_key"], module.params[
|
||||
"account_secret"], module.params["domain"], module.params["sandbox"], module)
|
||||
state = module.params["state"]
|
||||
record_name = module.params["record_name"]
|
||||
record_type = module.params["record_type"]
|
||||
record_value = module.params["record_value"]
|
||||
|
||||
# Follow Keyword Controlled Behavior
|
||||
if record_name is None:
|
||||
domain_records = DME.getRecords()
|
||||
if not domain_records:
|
||||
module.fail_json(
|
||||
msg="The requested domain name is not accessible with this api_key; try using its ID if known.")
|
||||
module.exit_json(changed=False, result=domain_records)
|
||||
|
||||
# Fetch existing record + Build new one
|
||||
current_record = DME.getMatchingRecord(record_name, record_type, record_value)
|
||||
new_record = {'name': record_name}
|
||||
for i in ["record_value", "record_type", "record_ttl"]:
|
||||
if not module.params[i] is None:
|
||||
new_record[i[len("record_"):]] = module.params[i]
|
||||
# Special handling for mx record
|
||||
if new_record["type"] == "MX":
|
||||
new_record["mxLevel"] = new_record["value"].split(" ")[0]
|
||||
new_record["value"] = new_record["value"].split(" ")[1]
|
||||
|
||||
# Special handling for SRV records
|
||||
if new_record["type"] == "SRV":
|
||||
new_record["priority"] = new_record["value"].split(" ")[0]
|
||||
new_record["weight"] = new_record["value"].split(" ")[1]
|
||||
new_record["port"] = new_record["value"].split(" ")[2]
|
||||
new_record["value"] = new_record["value"].split(" ")[3]
|
||||
|
||||
# Fetch existing monitor if the A record indicates it should exist and build the new monitor
|
||||
current_monitor = dict()
|
||||
new_monitor = dict()
|
||||
if current_record and current_record['type'] == 'A':
|
||||
current_monitor = DME.getMonitor(current_record['id'])
|
||||
|
||||
# Build the new monitor
|
||||
for i in ['monitor', 'systemDescription', 'protocol', 'port', 'sensitivity', 'maxEmails',
|
||||
'contactList', 'httpFqdn', 'httpFile', 'httpQueryString',
|
||||
'failover', 'autoFailover', 'ip1', 'ip2', 'ip3', 'ip4', 'ip5']:
|
||||
if module.params[i] is not None:
|
||||
if i == 'protocol':
|
||||
# The API requires protocol to be a numeric in the range 1-6
|
||||
new_monitor['protocolId'] = protocols[module.params[i]]
|
||||
elif i == 'sensitivity':
|
||||
# The API requires sensitivity to be a numeric of 8, 5, or 3
|
||||
new_monitor[i] = sensitivities[module.params[i]]
|
||||
elif i == 'contactList':
|
||||
# The module accepts either the name or the id of the contact list
|
||||
contact_list_id = module.params[i]
|
||||
if not contact_list_id.isdigit() and contact_list_id != '':
|
||||
contact_list = DME.getContactListByName(contact_list_id)
|
||||
if not contact_list:
|
||||
module.fail_json(msg="Contact list {0} does not exist".format(contact_list_id))
|
||||
contact_list_id = contact_list.get('id', '')
|
||||
new_monitor['contactListId'] = contact_list_id
|
||||
else:
|
||||
# The module option names match the API field names
|
||||
new_monitor[i] = module.params[i]
|
||||
|
||||
# Compare new record against existing one
|
||||
record_changed = False
|
||||
if current_record:
|
||||
for i in new_record:
|
||||
if str(current_record[i]) != str(new_record[i]):
|
||||
record_changed = True
|
||||
new_record['id'] = str(current_record['id'])
|
||||
|
||||
monitor_changed = False
|
||||
if current_monitor:
|
||||
for i in new_monitor:
|
||||
if str(current_monitor.get(i)) != str(new_monitor[i]):
|
||||
monitor_changed = True
|
||||
|
||||
# Follow Keyword Controlled Behavior
|
||||
if state == 'present':
|
||||
# return the record if no value is specified
|
||||
if "value" not in new_record:
|
||||
if not current_record:
|
||||
module.fail_json(
|
||||
msg="A record with name '%s' does not exist for domain '%s.'" % (record_name, module.params['domain']))
|
||||
module.exit_json(changed=False, result=dict(record=current_record, monitor=current_monitor))
|
||||
|
||||
# create record and monitor as the record does not exist
|
||||
if not current_record:
|
||||
record = DME.createRecord(DME.prepareRecord(new_record))
|
||||
monitor = DME.updateMonitor(record['id'], DME.prepareMonitor(new_monitor))
|
||||
module.exit_json(changed=True, result=dict(record=record, monitor=monitor))
|
||||
|
||||
# update the record
|
||||
updated = False
|
||||
if record_changed:
|
||||
DME.updateRecord(current_record['id'], DME.prepareRecord(new_record))
|
||||
updated = True
|
||||
if monitor_changed:
|
||||
DME.updateMonitor(current_monitor['recordId'], DME.prepareMonitor(new_monitor))
|
||||
updated = True
|
||||
if updated:
|
||||
module.exit_json(changed=True, result=dict(record=new_record, monitor=new_monitor))
|
||||
|
||||
# return the record (no changes)
|
||||
module.exit_json(changed=False, result=dict(record=current_record, monitor=current_monitor))
|
||||
|
||||
elif state == 'absent':
|
||||
changed = False
|
||||
# delete the record (and the monitor/failover) if it exists
|
||||
if current_record:
|
||||
DME.deleteRecord(current_record['id'])
|
||||
module.exit_json(changed=True)
|
||||
|
||||
# record does not exist, return w/o change.
|
||||
module.exit_json(changed=changed)
|
||||
|
||||
else:
|
||||
module.fail_json(
|
||||
msg="'%s' is an unknown value for the state argument" % state)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
210
plugins/modules/net_tools/exoscale/exo_dns_domain.py
Normal file
210
plugins/modules/net_tools/exoscale/exo_dns_domain.py
Normal file
@@ -0,0 +1,210 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2016, René Moser <mail@renemoser.net>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: exo_dns_domain
|
||||
short_description: Manages domain records on Exoscale DNS API.
|
||||
description:
|
||||
- Create and remove domain records.
|
||||
author: "René Moser (@resmo)"
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name of the record.
|
||||
required: true
|
||||
type: str
|
||||
state:
|
||||
description:
|
||||
- State of the resource.
|
||||
default: present
|
||||
choices: [ present, absent ]
|
||||
type: str
|
||||
extends_documentation_fragment:
|
||||
- community.general.exoscale
|
||||
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Create a domain
|
||||
exo_dns_domain:
|
||||
name: example.com
|
||||
|
||||
- name: Remove a domain
|
||||
exo_dns_domain:
|
||||
name: example.com
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
---
|
||||
exo_dns_domain:
|
||||
description: API domain results
|
||||
returned: success
|
||||
type: complex
|
||||
contains:
|
||||
account_id:
|
||||
description: Your account ID
|
||||
returned: success
|
||||
type: int
|
||||
sample: 34569
|
||||
auto_renew:
|
||||
description: Whether domain is auto renewed or not
|
||||
returned: success
|
||||
type: bool
|
||||
sample: false
|
||||
created_at:
|
||||
description: When the domain was created
|
||||
returned: success
|
||||
type: str
|
||||
sample: "2016-08-12T15:24:23.989Z"
|
||||
expires_on:
|
||||
description: When the domain expires
|
||||
returned: success
|
||||
type: str
|
||||
sample: "2016-08-12T15:24:23.989Z"
|
||||
id:
|
||||
description: ID of the domain
|
||||
returned: success
|
||||
type: int
|
||||
sample: "2016-08-12T15:24:23.989Z"
|
||||
lockable:
|
||||
description: Whether the domain is lockable or not
|
||||
returned: success
|
||||
type: bool
|
||||
sample: true
|
||||
name:
|
||||
description: Domain name
|
||||
returned: success
|
||||
type: str
|
||||
sample: example.com
|
||||
record_count:
|
||||
description: Number of records related to this domain
|
||||
returned: success
|
||||
type: int
|
||||
sample: 5
|
||||
registrant_id:
|
||||
description: ID of the registrant
|
||||
returned: success
|
||||
type: int
|
||||
sample: null
|
||||
service_count:
|
||||
description: Number of services
|
||||
returned: success
|
||||
type: int
|
||||
sample: 0
|
||||
state:
|
||||
description: State of the domain
|
||||
returned: success
|
||||
type: str
|
||||
sample: "hosted"
|
||||
token:
|
||||
description: Token
|
||||
returned: success
|
||||
type: str
|
||||
sample: "r4NzTRp6opIeFKfaFYvOd6MlhGyD07jl"
|
||||
unicode_name:
|
||||
description: Domain name as unicode
|
||||
returned: success
|
||||
type: str
|
||||
sample: "example.com"
|
||||
updated_at:
|
||||
description: When the domain was updated last.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "2016-08-12T15:24:23.989Z"
|
||||
user_id:
|
||||
description: ID of the user
|
||||
returned: success
|
||||
type: int
|
||||
sample: null
|
||||
whois_protected:
|
||||
description: Whether the whois is protected or not
|
||||
returned: success
|
||||
type: bool
|
||||
sample: false
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible_collections.community.general.plugins.module_utils.exoscale import ExoDns, exo_dns_argument_spec, exo_dns_required_together
|
||||
|
||||
|
||||
class ExoDnsDomain(ExoDns):
|
||||
|
||||
def __init__(self, module):
|
||||
super(ExoDnsDomain, self).__init__(module)
|
||||
self.name = self.module.params.get('name').lower()
|
||||
|
||||
def get_domain(self):
|
||||
domains = self.api_query("/domains", "GET")
|
||||
for z in domains:
|
||||
if z['domain']['name'].lower() == self.name:
|
||||
return z
|
||||
return None
|
||||
|
||||
def present_domain(self):
|
||||
domain = self.get_domain()
|
||||
data = {
|
||||
'domain': {
|
||||
'name': self.name,
|
||||
}
|
||||
}
|
||||
if not domain:
|
||||
self.result['diff']['after'] = data['domain']
|
||||
self.result['changed'] = True
|
||||
if not self.module.check_mode:
|
||||
domain = self.api_query("/domains", "POST", data)
|
||||
return domain
|
||||
|
||||
def absent_domain(self):
|
||||
domain = self.get_domain()
|
||||
if domain:
|
||||
self.result['diff']['before'] = domain
|
||||
self.result['changed'] = True
|
||||
if not self.module.check_mode:
|
||||
self.api_query("/domains/%s" % domain['domain']['name'], "DELETE")
|
||||
return domain
|
||||
|
||||
def get_result(self, resource):
|
||||
if resource:
|
||||
self.result['exo_dns_domain'] = resource['domain']
|
||||
return self.result
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = exo_dns_argument_spec()
|
||||
argument_spec.update(dict(
|
||||
name=dict(type='str', required=True),
|
||||
state=dict(type='str', choices=['present', 'absent'], default='present'),
|
||||
))
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
required_together=exo_dns_required_together(),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
exo_dns_domain = ExoDnsDomain(module)
|
||||
if module.params.get('state') == "present":
|
||||
resource = exo_dns_domain.present_domain()
|
||||
else:
|
||||
resource = exo_dns_domain.absent_domain()
|
||||
result = exo_dns_domain.get_result(resource)
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
345
plugins/modules/net_tools/exoscale/exo_dns_record.py
Normal file
345
plugins/modules/net_tools/exoscale/exo_dns_record.py
Normal file
@@ -0,0 +1,345 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2016, René Moser <mail@renemoser.net>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: exo_dns_record
|
||||
short_description: Manages DNS records on Exoscale DNS.
|
||||
description:
|
||||
- Create, update and delete records.
|
||||
author: "René Moser (@resmo)"
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name of the record.
|
||||
default: ""
|
||||
type: str
|
||||
domain:
|
||||
description:
|
||||
- Domain the record is related to.
|
||||
required: true
|
||||
type: str
|
||||
record_type:
|
||||
description:
|
||||
- Type of the record.
|
||||
default: A
|
||||
choices: [ A, ALIAS, CNAME, MX, SPF, URL, TXT, NS, SRV, NAPTR, PTR, AAAA, SSHFP, HINFO, POOL ]
|
||||
aliases: [ rtype, type ]
|
||||
type: str
|
||||
content:
|
||||
description:
|
||||
- Content of the record.
|
||||
- Required if C(state=present) or C(multiple=yes).
|
||||
aliases: [ value, address ]
|
||||
type: str
|
||||
ttl:
|
||||
description:
|
||||
- TTL of the record in seconds.
|
||||
default: 3600
|
||||
type: int
|
||||
prio:
|
||||
description:
|
||||
- Priority of the record.
|
||||
aliases: [ priority ]
|
||||
type: int
|
||||
multiple:
|
||||
description:
|
||||
- Whether there are more than one records with similar I(name) and I(record_type).
|
||||
- Only allowed for a few record types, e.g. C(record_type=A), C(record_type=NS) or C(record_type=MX).
|
||||
- I(content) will not be updated, instead it is used as a key to find existing records.
|
||||
type: bool
|
||||
default: no
|
||||
state:
|
||||
description:
|
||||
- State of the record.
|
||||
default: present
|
||||
choices: [ present, absent ]
|
||||
type: str
|
||||
extends_documentation_fragment:
|
||||
- community.general.exoscale
|
||||
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Create or update an A record
|
||||
exo_dns_record:
|
||||
name: web-vm-1
|
||||
domain: example.com
|
||||
content: 1.2.3.4
|
||||
|
||||
- name: Update an existing A record with a new IP
|
||||
exo_dns_record:
|
||||
name: web-vm-1
|
||||
domain: example.com
|
||||
content: 1.2.3.5
|
||||
|
||||
- name: Create another A record with same name
|
||||
exo_dns_record:
|
||||
name: web-vm-1
|
||||
domain: example.com
|
||||
content: 1.2.3.6
|
||||
multiple: yes
|
||||
|
||||
- name: Create or update a CNAME record
|
||||
exo_dns_record:
|
||||
name: www
|
||||
domain: example.com
|
||||
record_type: CNAME
|
||||
content: web-vm-1
|
||||
|
||||
- name: Create another MX record
|
||||
exo_dns_record:
|
||||
domain: example.com
|
||||
record_type: MX
|
||||
content: mx1.example.com
|
||||
prio: 10
|
||||
multiple: yes
|
||||
|
||||
- name: Delete one MX record out of multiple
|
||||
exo_dns_record:
|
||||
domain: example.com
|
||||
record_type: MX
|
||||
content: mx1.example.com
|
||||
multiple: yes
|
||||
state: absent
|
||||
|
||||
- name: Remove a single A record
|
||||
exo_dns_record:
|
||||
name: www
|
||||
domain: example.com
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
---
|
||||
exo_dns_record:
|
||||
description: API record results
|
||||
returned: success
|
||||
type: complex
|
||||
contains:
|
||||
content:
|
||||
description: value of the record
|
||||
returned: success
|
||||
type: str
|
||||
sample: 1.2.3.4
|
||||
created_at:
|
||||
description: When the record was created
|
||||
returned: success
|
||||
type: str
|
||||
sample: "2016-08-12T15:24:23.989Z"
|
||||
domain:
|
||||
description: Name of the domain
|
||||
returned: success
|
||||
type: str
|
||||
sample: example.com
|
||||
domain_id:
|
||||
description: ID of the domain
|
||||
returned: success
|
||||
type: int
|
||||
sample: 254324
|
||||
id:
|
||||
description: ID of the record
|
||||
returned: success
|
||||
type: int
|
||||
sample: 254324
|
||||
name:
|
||||
description: name of the record
|
||||
returned: success
|
||||
type: str
|
||||
sample: www
|
||||
parent_id:
|
||||
description: ID of the parent
|
||||
returned: success
|
||||
type: int
|
||||
sample: null
|
||||
prio:
|
||||
description: Priority of the record
|
||||
returned: success
|
||||
type: int
|
||||
sample: 10
|
||||
record_type:
|
||||
description: Priority of the record
|
||||
returned: success
|
||||
type: str
|
||||
sample: A
|
||||
system_record:
|
||||
description: Whether the record is a system record or not
|
||||
returned: success
|
||||
type: bool
|
||||
sample: false
|
||||
ttl:
|
||||
description: Time to live of the record
|
||||
returned: success
|
||||
type: int
|
||||
sample: 3600
|
||||
updated_at:
|
||||
description: When the record was updated
|
||||
returned: success
|
||||
type: str
|
||||
sample: "2016-08-12T15:24:23.989Z"
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible_collections.community.general.plugins.module_utils.exoscale import ExoDns, exo_dns_argument_spec, exo_dns_required_together
|
||||
|
||||
|
||||
EXO_RECORD_TYPES = [
|
||||
'A',
|
||||
'ALIAS',
|
||||
'CNAME',
|
||||
'MX',
|
||||
'SPF',
|
||||
'URL',
|
||||
'TXT',
|
||||
'NS',
|
||||
'SRV',
|
||||
'NAPTR',
|
||||
'PTR',
|
||||
'AAAA',
|
||||
'SSHFP',
|
||||
'HINFO',
|
||||
'POOL'
|
||||
]
|
||||
|
||||
|
||||
class ExoDnsRecord(ExoDns):
|
||||
|
||||
def __init__(self, module):
|
||||
super(ExoDnsRecord, self).__init__(module)
|
||||
|
||||
self.domain = self.module.params.get('domain').lower()
|
||||
self.name = self.module.params.get('name').lower()
|
||||
if self.name == self.domain:
|
||||
self.name = ""
|
||||
|
||||
self.multiple = self.module.params.get('multiple')
|
||||
self.record_type = self.module.params.get('record_type')
|
||||
self.content = self.module.params.get('content')
|
||||
|
||||
def _create_record(self, record):
|
||||
self.result['changed'] = True
|
||||
data = {
|
||||
'record': {
|
||||
'name': self.name,
|
||||
'record_type': self.record_type,
|
||||
'content': self.content,
|
||||
'ttl': self.module.params.get('ttl'),
|
||||
'prio': self.module.params.get('prio'),
|
||||
}
|
||||
}
|
||||
self.result['diff']['after'] = data['record']
|
||||
if not self.module.check_mode:
|
||||
record = self.api_query("/domains/%s/records" % self.domain, "POST", data)
|
||||
return record
|
||||
|
||||
def _update_record(self, record):
|
||||
data = {
|
||||
'record': {
|
||||
'name': self.name,
|
||||
'content': self.content,
|
||||
'ttl': self.module.params.get('ttl'),
|
||||
'prio': self.module.params.get('prio'),
|
||||
}
|
||||
}
|
||||
if self.has_changed(data['record'], record['record']):
|
||||
self.result['changed'] = True
|
||||
if not self.module.check_mode:
|
||||
record = self.api_query("/domains/%s/records/%s" % (self.domain, record['record']['id']), "PUT", data)
|
||||
return record
|
||||
|
||||
def get_record(self):
|
||||
domain = self.module.params.get('domain')
|
||||
records = self.api_query("/domains/%s/records" % domain, "GET")
|
||||
|
||||
result = {}
|
||||
for r in records:
|
||||
|
||||
if r['record']['record_type'] != self.record_type:
|
||||
continue
|
||||
|
||||
r_name = r['record']['name'].lower()
|
||||
r_content = r['record']['content']
|
||||
|
||||
if r_name == self.name:
|
||||
if not self.multiple:
|
||||
if result:
|
||||
self.module.fail_json(msg="More than one record with record_type=%s and name=%s params. "
|
||||
"Use multiple=yes for more than one record." % (self.record_type, self.name))
|
||||
else:
|
||||
result = r
|
||||
elif r_content == self.content:
|
||||
return r
|
||||
|
||||
return result
|
||||
|
||||
def present_record(self):
|
||||
record = self.get_record()
|
||||
if not record:
|
||||
record = self._create_record(record)
|
||||
else:
|
||||
record = self._update_record(record)
|
||||
return record
|
||||
|
||||
def absent_record(self):
|
||||
record = self.get_record()
|
||||
if record:
|
||||
self.result['diff']['before'] = record
|
||||
self.result['changed'] = True
|
||||
if not self.module.check_mode:
|
||||
self.api_query("/domains/%s/records/%s" % (self.domain, record['record']['id']), "DELETE")
|
||||
return record
|
||||
|
||||
def get_result(self, resource):
|
||||
if resource:
|
||||
self.result['exo_dns_record'] = resource['record']
|
||||
self.result['exo_dns_record']['domain'] = self.domain
|
||||
return self.result
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = exo_dns_argument_spec()
|
||||
argument_spec.update(dict(
|
||||
name=dict(type='str', default=''),
|
||||
record_type=dict(type='str', choices=EXO_RECORD_TYPES, aliases=['rtype', 'type'], default='A'),
|
||||
content=dict(type='str', aliases=['value', 'address']),
|
||||
multiple=(dict(type='bool', default=False)),
|
||||
ttl=dict(type='int', default=3600),
|
||||
prio=dict(type='int', aliases=['priority']),
|
||||
domain=dict(type='str', required=True),
|
||||
state=dict(type='str', choices=['present', 'absent'], default='present'),
|
||||
))
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
required_together=exo_dns_required_together(),
|
||||
required_if=[
|
||||
('state', 'present', ['content']),
|
||||
('multiple', True, ['content']),
|
||||
],
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
exo_dns_record = ExoDnsRecord(module)
|
||||
if module.params.get('state') == "present":
|
||||
resource = exo_dns_record.present_record()
|
||||
else:
|
||||
resource = exo_dns_record.absent_record()
|
||||
|
||||
result = exo_dns_record.get_result(resource)
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
450
plugins/modules/net_tools/haproxy.py
Normal file
450
plugins/modules/net_tools/haproxy.py
Normal file
@@ -0,0 +1,450 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2014, Ravi Bhure <ravibhure@gmail.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: haproxy
|
||||
short_description: Enable, disable, and set weights for HAProxy backend servers using socket commands
|
||||
author:
|
||||
- Ravi Bhure (@ravibhure)
|
||||
description:
|
||||
- Enable, disable, drain and set weights for HAProxy backend servers using socket commands.
|
||||
notes:
|
||||
- Enable, disable and drain commands are restricted and can only be issued on
|
||||
sockets configured for level 'admin'. For example, you can add the line
|
||||
'stats socket /var/run/haproxy.sock level admin' to the general section of
|
||||
haproxy.cfg. See U(http://haproxy.1wt.eu/download/1.5/doc/configuration.txt).
|
||||
- Depends on netcat (nc) being available; you need to install the appropriate
|
||||
package for your operating system before this module can be used.
|
||||
options:
|
||||
backend:
|
||||
description:
|
||||
- Name of the HAProxy backend pool.
|
||||
- If this parameter is unset, it will be auto-detected.
|
||||
type: str
|
||||
drain:
|
||||
description:
|
||||
- Wait until the server has no active connections or until the timeout
|
||||
determined by wait_interval and wait_retries is reached.
|
||||
- Continue only after the status changes to 'MAINT'.
|
||||
- This overrides the shutdown_sessions option.
|
||||
type: bool
|
||||
host:
|
||||
description:
|
||||
- Name of the backend host to change.
|
||||
type: str
|
||||
required: true
|
||||
shutdown_sessions:
|
||||
description:
|
||||
- When disabling a server, immediately terminate all the sessions attached
|
||||
to the specified server.
|
||||
- This can be used to terminate long-running sessions after a server is put
|
||||
into maintenance mode. Overridden by the drain option.
|
||||
type: bool
|
||||
default: no
|
||||
socket:
|
||||
description:
|
||||
- Path to the HAProxy socket file.
|
||||
type: path
|
||||
default: /var/run/haproxy.sock
|
||||
state:
|
||||
description:
|
||||
- Desired state of the provided backend host.
|
||||
- Note that C(drain) state was added in version 2.4.
|
||||
- It is supported only by HAProxy version 1.5 or later,
|
||||
- When used on versions < 1.5, it will be ignored.
|
||||
type: str
|
||||
required: true
|
||||
choices: [ disabled, drain, enabled ]
|
||||
fail_on_not_found:
|
||||
description:
|
||||
- Fail whenever trying to enable/disable a backend host that does not exist
|
||||
type: bool
|
||||
default: no
|
||||
wait:
|
||||
description:
|
||||
- Wait until the server reports a status of 'UP' when C(state=enabled),
|
||||
status of 'MAINT' when C(state=disabled) or status of 'DRAIN' when C(state=drain)
|
||||
type: bool
|
||||
default: no
|
||||
wait_interval:
|
||||
description:
|
||||
- Number of seconds to wait between retries.
|
||||
type: int
|
||||
default: 5
|
||||
wait_retries:
|
||||
description:
|
||||
- Number of times to check for status after changing the state.
|
||||
type: int
|
||||
default: 25
|
||||
weight:
|
||||
description:
|
||||
- The value passed in argument.
|
||||
- If the value ends with the `%` sign, then the new weight will be
|
||||
relative to the initially configured weight.
|
||||
- Relative weights are only permitted between 0 and 100% and absolute
|
||||
weights are permitted between 0 and 256.
|
||||
type: str
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Disable server in 'www' backend pool
|
||||
haproxy:
|
||||
state: disabled
|
||||
host: '{{ inventory_hostname }}'
|
||||
backend: www
|
||||
|
||||
- name: Disable server without backend pool name (apply to all available backend pool)
|
||||
haproxy:
|
||||
state: disabled
|
||||
host: '{{ inventory_hostname }}'
|
||||
|
||||
- name: Disable server, provide socket file
|
||||
haproxy:
|
||||
state: disabled
|
||||
host: '{{ inventory_hostname }}'
|
||||
socket: /var/run/haproxy.sock
|
||||
backend: www
|
||||
|
||||
- name: Disable server, provide socket file, wait until status reports in maintenance
|
||||
haproxy:
|
||||
state: disabled
|
||||
host: '{{ inventory_hostname }}'
|
||||
socket: /var/run/haproxy.sock
|
||||
backend: www
|
||||
wait: yes
|
||||
|
||||
# Place server in drain mode, providing a socket file. Then check the server's
|
||||
# status every minute to see if it changes to maintenance mode, continuing if it
|
||||
# does in an hour and failing otherwise.
|
||||
- haproxy:
|
||||
state: disabled
|
||||
host: '{{ inventory_hostname }}'
|
||||
socket: /var/run/haproxy.sock
|
||||
backend: www
|
||||
wait: yes
|
||||
drain: yes
|
||||
wait_interval: 1
|
||||
wait_retries: 60
|
||||
|
||||
- name: Disable backend server in 'www' backend pool and drop open sessions to it
|
||||
haproxy:
|
||||
state: disabled
|
||||
host: '{{ inventory_hostname }}'
|
||||
backend: www
|
||||
socket: /var/run/haproxy.sock
|
||||
shutdown_sessions: yes
|
||||
|
||||
- name: Disable server without backend pool name (apply to all available backend pool) but fail when the backend host is not found
|
||||
haproxy:
|
||||
state: disabled
|
||||
host: '{{ inventory_hostname }}'
|
||||
fail_on_not_found: yes
|
||||
|
||||
- name: Enable server in 'www' backend pool
|
||||
haproxy:
|
||||
state: enabled
|
||||
host: '{{ inventory_hostname }}'
|
||||
backend: www
|
||||
|
||||
- name: Enable server in 'www' backend pool wait until healthy
|
||||
haproxy:
|
||||
state: enabled
|
||||
host: '{{ inventory_hostname }}'
|
||||
backend: www
|
||||
wait: yes
|
||||
|
||||
- name: Enable server in 'www' backend pool wait until healthy. Retry 10 times with intervals of 5 seconds to retrieve the health
|
||||
haproxy:
|
||||
state: enabled
|
||||
host: '{{ inventory_hostname }}'
|
||||
backend: www
|
||||
wait: yes
|
||||
wait_retries: 10
|
||||
wait_interval: 5
|
||||
|
||||
- name: Enable server in 'www' backend pool with change server(s) weight
|
||||
haproxy:
|
||||
state: enabled
|
||||
host: '{{ inventory_hostname }}'
|
||||
socket: /var/run/haproxy.sock
|
||||
weight: 10
|
||||
backend: www
|
||||
|
||||
- name: Set the server in 'www' backend pool to drain mode
|
||||
haproxy:
|
||||
state: drain
|
||||
host: '{{ inventory_hostname }}'
|
||||
socket: /var/run/haproxy.sock
|
||||
backend: www
|
||||
'''
|
||||
|
||||
import csv
|
||||
import socket
|
||||
import time
|
||||
from string import Template
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils._text import to_bytes, to_text
|
||||
|
||||
|
||||
DEFAULT_SOCKET_LOCATION = "/var/run/haproxy.sock"
|
||||
RECV_SIZE = 1024
|
||||
ACTION_CHOICES = ['enabled', 'disabled', 'drain']
|
||||
WAIT_RETRIES = 25
|
||||
WAIT_INTERVAL = 5
|
||||
|
||||
|
||||
######################################################################
|
||||
class TimeoutException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class HAProxy(object):
|
||||
"""
|
||||
Used for communicating with HAProxy through its local UNIX socket interface.
|
||||
Perform common tasks in Haproxy related to enable server and
|
||||
disable server.
|
||||
|
||||
The complete set of external commands Haproxy handles is documented
|
||||
on their website:
|
||||
|
||||
http://haproxy.1wt.eu/download/1.5/doc/configuration.txt#Unix Socket commands
|
||||
"""
|
||||
|
||||
def __init__(self, module):
|
||||
self.module = module
|
||||
|
||||
self.state = self.module.params['state']
|
||||
self.host = self.module.params['host']
|
||||
self.backend = self.module.params['backend']
|
||||
self.weight = self.module.params['weight']
|
||||
self.socket = self.module.params['socket']
|
||||
self.shutdown_sessions = self.module.params['shutdown_sessions']
|
||||
self.fail_on_not_found = self.module.params['fail_on_not_found']
|
||||
self.wait = self.module.params['wait']
|
||||
self.wait_retries = self.module.params['wait_retries']
|
||||
self.wait_interval = self.module.params['wait_interval']
|
||||
self._drain = self.module.params['drain']
|
||||
self.command_results = {}
|
||||
|
||||
def execute(self, cmd, timeout=200, capture_output=True):
|
||||
"""
|
||||
Executes a HAProxy command by sending a message to a HAProxy's local
|
||||
UNIX socket and waiting up to 'timeout' milliseconds for the response.
|
||||
"""
|
||||
self.client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
self.client.connect(self.socket)
|
||||
self.client.sendall(to_bytes('%s\n' % cmd))
|
||||
|
||||
result = b''
|
||||
buf = b''
|
||||
buf = self.client.recv(RECV_SIZE)
|
||||
while buf:
|
||||
result += buf
|
||||
buf = self.client.recv(RECV_SIZE)
|
||||
result = to_text(result, errors='surrogate_or_strict')
|
||||
|
||||
if capture_output:
|
||||
self.capture_command_output(cmd, result.strip())
|
||||
self.client.close()
|
||||
return result
|
||||
|
||||
def capture_command_output(self, cmd, output):
|
||||
"""
|
||||
Capture the output for a command
|
||||
"""
|
||||
if 'command' not in self.command_results:
|
||||
self.command_results['command'] = []
|
||||
self.command_results['command'].append(cmd)
|
||||
if 'output' not in self.command_results:
|
||||
self.command_results['output'] = []
|
||||
self.command_results['output'].append(output)
|
||||
|
||||
def discover_all_backends(self):
|
||||
"""
|
||||
Discover all entries with svname = 'BACKEND' and return a list of their corresponding
|
||||
pxnames
|
||||
"""
|
||||
data = self.execute('show stat', 200, False).lstrip('# ')
|
||||
r = csv.DictReader(data.splitlines())
|
||||
return tuple(map(lambda d: d['pxname'], filter(lambda d: d['svname'] == 'BACKEND', r)))
|
||||
|
||||
def discover_version(self):
|
||||
"""
|
||||
Attempt to extract the haproxy version.
|
||||
Return a tuple containing major and minor version.
|
||||
"""
|
||||
data = self.execute('show info', 200, False)
|
||||
lines = data.splitlines()
|
||||
line = [x for x in lines if 'Version:' in x]
|
||||
try:
|
||||
version_values = line[0].partition(':')[2].strip().split('.', 3)
|
||||
version = (int(version_values[0]), int(version_values[1]))
|
||||
except (ValueError, TypeError, IndexError):
|
||||
version = None
|
||||
|
||||
return version
|
||||
|
||||
def execute_for_backends(self, cmd, pxname, svname, wait_for_status=None):
|
||||
"""
|
||||
Run some command on the specified backends. If no backends are provided they will
|
||||
be discovered automatically (all backends)
|
||||
"""
|
||||
# Discover backends if none are given
|
||||
if pxname is None:
|
||||
backends = self.discover_all_backends()
|
||||
else:
|
||||
backends = [pxname]
|
||||
|
||||
# Run the command for each requested backend
|
||||
for backend in backends:
|
||||
# Fail when backends were not found
|
||||
state = self.get_state_for(backend, svname)
|
||||
if (self.fail_on_not_found) and state is None:
|
||||
self.module.fail_json(
|
||||
msg="The specified backend '%s/%s' was not found!" % (backend, svname))
|
||||
|
||||
if state is not None:
|
||||
self.execute(Template(cmd).substitute(pxname=backend, svname=svname))
|
||||
if self.wait:
|
||||
self.wait_until_status(backend, svname, wait_for_status)
|
||||
|
||||
def get_state_for(self, pxname, svname):
|
||||
"""
|
||||
Find the state of specific services. When pxname is not set, get all backends for a specific host.
|
||||
Returns a list of dictionaries containing the status and weight for those services.
|
||||
"""
|
||||
data = self.execute('show stat', 200, False).lstrip('# ')
|
||||
r = csv.DictReader(data.splitlines())
|
||||
state = tuple(
|
||||
map(
|
||||
lambda d: {'status': d['status'], 'weight': d['weight'], 'scur': d['scur']},
|
||||
filter(lambda d: (pxname is None or d['pxname']
|
||||
== pxname) and d['svname'] == svname, r)
|
||||
)
|
||||
)
|
||||
return state or None
|
||||
|
||||
def wait_until_status(self, pxname, svname, status):
|
||||
"""
|
||||
Wait for a service to reach the specified status. Try RETRIES times
|
||||
with INTERVAL seconds of sleep in between. If the service has not reached
|
||||
the expected status in that time, the module will fail. If the service was
|
||||
not found, the module will fail.
|
||||
"""
|
||||
for i in range(1, self.wait_retries):
|
||||
state = self.get_state_for(pxname, svname)
|
||||
|
||||
# We can assume there will only be 1 element in state because both svname and pxname are always set when we get here
|
||||
# When using track we get a status like this: MAINT (via pxname/svname) so we need to do substring matching
|
||||
if status in state[0]['status']:
|
||||
if not self._drain or (state[0]['scur'] == '0' and 'MAINT' in state):
|
||||
return True
|
||||
else:
|
||||
time.sleep(self.wait_interval)
|
||||
|
||||
self.module.fail_json(msg="server %s/%s not status '%s' after %d retries. Aborting." %
|
||||
(pxname, svname, status, self.wait_retries))
|
||||
|
||||
def enabled(self, host, backend, weight):
|
||||
"""
|
||||
Enabled action, marks server to UP and checks are re-enabled,
|
||||
also supports to get current weight for server (default) and
|
||||
set the weight for haproxy backend server when provides.
|
||||
"""
|
||||
cmd = "get weight $pxname/$svname; enable server $pxname/$svname"
|
||||
if weight:
|
||||
cmd += "; set weight $pxname/$svname %s" % weight
|
||||
self.execute_for_backends(cmd, backend, host, 'UP')
|
||||
|
||||
def disabled(self, host, backend, shutdown_sessions):
|
||||
"""
|
||||
Disabled action, marks server to DOWN for maintenance. In this mode, no more checks will be
|
||||
performed on the server until it leaves maintenance,
|
||||
also it shutdown sessions while disabling backend host server.
|
||||
"""
|
||||
cmd = "get weight $pxname/$svname; disable server $pxname/$svname"
|
||||
if shutdown_sessions:
|
||||
cmd += "; shutdown sessions server $pxname/$svname"
|
||||
self.execute_for_backends(cmd, backend, host, 'MAINT')
|
||||
|
||||
def drain(self, host, backend, status='DRAIN'):
|
||||
"""
|
||||
Drain action, sets the server to DRAIN mode.
|
||||
In this mode mode, the server will not accept any new connections
|
||||
other than those that are accepted via persistence.
|
||||
"""
|
||||
haproxy_version = self.discover_version()
|
||||
|
||||
# check if haproxy version suppots DRAIN state (starting with 1.5)
|
||||
if haproxy_version and (1, 5) <= haproxy_version:
|
||||
cmd = "set server $pxname/$svname state drain"
|
||||
self.execute_for_backends(cmd, backend, host, status)
|
||||
|
||||
def act(self):
|
||||
"""
|
||||
Figure out what you want to do from ansible, and then do it.
|
||||
"""
|
||||
# Get the state before the run
|
||||
self.command_results['state_before'] = self.get_state_for(self.backend, self.host)
|
||||
|
||||
# toggle enable/disbale server
|
||||
if self.state == 'enabled':
|
||||
self.enabled(self.host, self.backend, self.weight)
|
||||
elif self.state == 'disabled' and self._drain:
|
||||
self.drain(self.host, self.backend, status='MAINT')
|
||||
elif self.state == 'disabled':
|
||||
self.disabled(self.host, self.backend, self.shutdown_sessions)
|
||||
elif self.state == 'drain':
|
||||
self.drain(self.host, self.backend)
|
||||
else:
|
||||
self.module.fail_json(msg="unknown state specified: '%s'" % self.state)
|
||||
|
||||
# Get the state after the run
|
||||
self.command_results['state_after'] = self.get_state_for(self.backend, self.host)
|
||||
|
||||
# Report change status
|
||||
self.command_results['changed'] = (self.command_results['state_before'] != self.command_results['state_after'])
|
||||
|
||||
self.module.exit_json(**self.command_results)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
# load ansible module object
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
state=dict(type='str', required=True, choices=ACTION_CHOICES),
|
||||
host=dict(type='str', required=True),
|
||||
backend=dict(type='str'),
|
||||
weight=dict(type='str'),
|
||||
socket=dict(type='path', default=DEFAULT_SOCKET_LOCATION),
|
||||
shutdown_sessions=dict(type='bool', default=False),
|
||||
fail_on_not_found=dict(type='bool', default=False),
|
||||
wait=dict(type='bool', default=False),
|
||||
wait_retries=dict(type='int', default=WAIT_RETRIES),
|
||||
wait_interval=dict(type='int', default=WAIT_INTERVAL),
|
||||
drain=dict(type='bool', default=False),
|
||||
),
|
||||
)
|
||||
|
||||
if not socket:
|
||||
module.fail_json(msg="unable to locate haproxy socket")
|
||||
|
||||
ansible_haproxy = HAProxy(module)
|
||||
ansible_haproxy.act()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
146
plugins/modules/net_tools/hetzner_failover_ip.py
Normal file
146
plugins/modules/net_tools/hetzner_failover_ip.py
Normal file
@@ -0,0 +1,146 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2019 Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: hetzner_failover_ip
|
||||
short_description: Manage Hetzner's failover IPs
|
||||
author:
|
||||
- Felix Fontein (@felixfontein)
|
||||
description:
|
||||
- Manage Hetzner's failover IPs.
|
||||
seealso:
|
||||
- name: Failover IP documentation
|
||||
description: Hetzner's documentation on failover IPs.
|
||||
link: https://wiki.hetzner.de/index.php/Failover/en
|
||||
- module: hetzner_failover_ip_info
|
||||
description: Retrieve information on failover IPs.
|
||||
extends_documentation_fragment:
|
||||
- community.general.hetzner
|
||||
|
||||
options:
|
||||
failover_ip:
|
||||
description: The failover IP address.
|
||||
type: str
|
||||
required: yes
|
||||
state:
|
||||
description:
|
||||
- Defines whether the IP will be routed or not.
|
||||
- If set to C(routed), I(value) must be specified.
|
||||
type: str
|
||||
choices:
|
||||
- routed
|
||||
- unrouted
|
||||
default: routed
|
||||
value:
|
||||
description:
|
||||
- The new value for the failover IP address.
|
||||
- Required when setting I(state) to C(routed).
|
||||
type: str
|
||||
timeout:
|
||||
description:
|
||||
- Timeout to use when routing or unrouting the failover IP.
|
||||
- Note that the API call returns when the failover IP has been
|
||||
successfully routed to the new address, respectively successfully
|
||||
unrouted.
|
||||
type: int
|
||||
default: 180
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Set value of failover IP 1.2.3.4 to 5.6.7.8
|
||||
hetzner_failover_ip:
|
||||
hetzner_user: foo
|
||||
hetzner_password: bar
|
||||
failover_ip: 1.2.3.4
|
||||
value: 5.6.7.8
|
||||
|
||||
- name: Set value of failover IP 1.2.3.4 to unrouted
|
||||
hetzner_failover_ip:
|
||||
hetzner_user: foo
|
||||
hetzner_password: bar
|
||||
failover_ip: 1.2.3.4
|
||||
state: unrouted
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
value:
|
||||
description:
|
||||
- The value of the failover IP.
|
||||
- Will be C(none) if the IP is unrouted.
|
||||
returned: success
|
||||
type: str
|
||||
state:
|
||||
description:
|
||||
- Will be C(routed) or C(unrouted).
|
||||
returned: success
|
||||
type: str
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible_collections.community.general.plugins.module_utils.hetzner import (
|
||||
HETZNER_DEFAULT_ARGUMENT_SPEC,
|
||||
get_failover,
|
||||
set_failover,
|
||||
get_failover_state,
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = dict(
|
||||
failover_ip=dict(type='str', required=True),
|
||||
state=dict(type='str', default='routed', choices=['routed', 'unrouted']),
|
||||
value=dict(type='str'),
|
||||
timeout=dict(type='int', default=180),
|
||||
)
|
||||
argument_spec.update(HETZNER_DEFAULT_ARGUMENT_SPEC)
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
required_if=(
|
||||
('state', 'routed', ['value']),
|
||||
),
|
||||
)
|
||||
|
||||
failover_ip = module.params['failover_ip']
|
||||
value = get_failover(module, failover_ip)
|
||||
changed = False
|
||||
before = get_failover_state(value)
|
||||
|
||||
if module.params['state'] == 'routed':
|
||||
new_value = module.params['value']
|
||||
else:
|
||||
new_value = None
|
||||
|
||||
if value != new_value:
|
||||
if module.check_mode:
|
||||
value = new_value
|
||||
changed = True
|
||||
else:
|
||||
value, changed = set_failover(module, failover_ip, new_value, timeout=module.params['timeout'])
|
||||
|
||||
after = get_failover_state(value)
|
||||
module.exit_json(
|
||||
changed=changed,
|
||||
diff=dict(
|
||||
before=before,
|
||||
after=after,
|
||||
),
|
||||
**after
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
122
plugins/modules/net_tools/hetzner_failover_ip_info.py
Normal file
122
plugins/modules/net_tools/hetzner_failover_ip_info.py
Normal file
@@ -0,0 +1,122 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2019 Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: hetzner_failover_ip_info
|
||||
short_description: Retrieve information on Hetzner's failover IPs
|
||||
author:
|
||||
- Felix Fontein (@felixfontein)
|
||||
description:
|
||||
- Retrieve information on Hetzner's failover IPs.
|
||||
seealso:
|
||||
- name: Failover IP documentation
|
||||
description: Hetzner's documentation on failover IPs.
|
||||
link: https://wiki.hetzner.de/index.php/Failover/en
|
||||
- module: hetzner_failover_ip
|
||||
description: Manage failover IPs.
|
||||
extends_documentation_fragment:
|
||||
- community.general.hetzner
|
||||
|
||||
options:
|
||||
failover_ip:
|
||||
description: The failover IP address.
|
||||
type: str
|
||||
required: yes
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Get value of failover IP 1.2.3.4
|
||||
hetzner_failover_ip_info:
|
||||
hetzner_user: foo
|
||||
hetzner_password: bar
|
||||
failover_ip: 1.2.3.4
|
||||
value: 5.6.7.8
|
||||
register: result
|
||||
|
||||
- name: Print value of failover IP 1.2.3.4 in case it is routed
|
||||
debug:
|
||||
msg: "1.2.3.4 routes to {{ result.value }}"
|
||||
when: result.state == 'routed'
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
value:
|
||||
description:
|
||||
- The value of the failover IP.
|
||||
- Will be C(none) if the IP is unrouted.
|
||||
returned: success
|
||||
type: str
|
||||
state:
|
||||
description:
|
||||
- Will be C(routed) or C(unrouted).
|
||||
returned: success
|
||||
type: str
|
||||
failover_ip:
|
||||
description:
|
||||
- The failover IP.
|
||||
returned: success
|
||||
type: str
|
||||
sample: '1.2.3.4'
|
||||
failover_netmask:
|
||||
description:
|
||||
- The netmask for the failover IP.
|
||||
returned: success
|
||||
type: str
|
||||
sample: '255.255.255.255'
|
||||
server_ip:
|
||||
description:
|
||||
- The main IP of the server this failover IP is associated to.
|
||||
- This is I(not) the server the failover IP is routed to.
|
||||
returned: success
|
||||
type: str
|
||||
server_number:
|
||||
description:
|
||||
- The number of the server this failover IP is associated to.
|
||||
- This is I(not) the server the failover IP is routed to.
|
||||
returned: success
|
||||
type: int
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible_collections.community.general.plugins.module_utils.hetzner import (
|
||||
HETZNER_DEFAULT_ARGUMENT_SPEC,
|
||||
get_failover_record,
|
||||
get_failover_state,
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = dict(
|
||||
failover_ip=dict(type='str', required=True),
|
||||
)
|
||||
argument_spec.update(HETZNER_DEFAULT_ARGUMENT_SPEC)
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
failover = get_failover_record(module, module.params['failover_ip'])
|
||||
result = get_failover_state(failover['active_server_ip'])
|
||||
result['failover_ip'] = failover['ip']
|
||||
result['failover_netmask'] = failover['netmask']
|
||||
result['server_ip'] = failover['server_ip']
|
||||
result['server_number'] = failover['server_number']
|
||||
result['changed'] = False
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
513
plugins/modules/net_tools/hetzner_firewall.py
Normal file
513
plugins/modules/net_tools/hetzner_firewall.py
Normal file
@@ -0,0 +1,513 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2019 Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: hetzner_firewall
|
||||
short_description: Manage Hetzner's dedicated server firewall
|
||||
author:
|
||||
- Felix Fontein (@felixfontein)
|
||||
description:
|
||||
- Manage Hetzner's dedicated server firewall.
|
||||
- Note that idempotency check for TCP flags simply compares strings and doesn't
|
||||
try to interpret the rules. This might change in the future.
|
||||
seealso:
|
||||
- name: Firewall documentation
|
||||
description: Hetzner's documentation on the stateless firewall for dedicated servers
|
||||
link: https://wiki.hetzner.de/index.php/Robot_Firewall/en
|
||||
- module: hetzner_firewall_info
|
||||
description: Retrieve information on firewall configuration.
|
||||
extends_documentation_fragment:
|
||||
- community.general.hetzner
|
||||
|
||||
options:
|
||||
server_ip:
|
||||
description: The server's main IP address.
|
||||
required: yes
|
||||
type: str
|
||||
port:
|
||||
description:
|
||||
- Switch port of firewall.
|
||||
type: str
|
||||
choices: [ main, kvm ]
|
||||
default: main
|
||||
state:
|
||||
description:
|
||||
- Status of the firewall.
|
||||
- Firewall is active if state is C(present), and disabled if state is C(absent).
|
||||
type: str
|
||||
default: present
|
||||
choices: [ present, absent ]
|
||||
whitelist_hos:
|
||||
description:
|
||||
- Whether Hetzner services have access.
|
||||
type: bool
|
||||
rules:
|
||||
description:
|
||||
- Firewall rules.
|
||||
type: dict
|
||||
suboptions:
|
||||
input:
|
||||
description:
|
||||
- Input firewall rules.
|
||||
type: list
|
||||
elements: dict
|
||||
suboptions:
|
||||
name:
|
||||
description:
|
||||
- Name of the firewall rule.
|
||||
type: str
|
||||
ip_version:
|
||||
description:
|
||||
- Internet protocol version.
|
||||
- Note that currently, only IPv4 is supported by Hetzner.
|
||||
required: yes
|
||||
type: str
|
||||
choices: [ ipv4, ipv6 ]
|
||||
dst_ip:
|
||||
description:
|
||||
- Destination IP address or subnet address.
|
||||
- CIDR notation.
|
||||
type: str
|
||||
dst_port:
|
||||
description:
|
||||
- Destination port or port range.
|
||||
type: str
|
||||
src_ip:
|
||||
description:
|
||||
- Source IP address or subnet address.
|
||||
- CIDR notation.
|
||||
type: str
|
||||
src_port:
|
||||
description:
|
||||
- Source port or port range.
|
||||
type: str
|
||||
protocol:
|
||||
description:
|
||||
- Protocol above IP layer
|
||||
type: str
|
||||
tcp_flags:
|
||||
description:
|
||||
- TCP flags or logical combination of flags.
|
||||
- Flags supported by Hetzner are C(syn), C(fin), C(rst), C(psh) and C(urg).
|
||||
- They can be combined with C(|) (logical or) and C(&) (logical and).
|
||||
- See L(the documentation,https://wiki.hetzner.de/index.php/Robot_Firewall/en#Parameter)
|
||||
for more information.
|
||||
type: str
|
||||
action:
|
||||
description:
|
||||
- Action if rule matches.
|
||||
required: yes
|
||||
type: str
|
||||
choices: [ accept, discard ]
|
||||
update_timeout:
|
||||
description:
|
||||
- Timeout to use when configuring the firewall.
|
||||
- Note that the API call returns before the firewall has been
|
||||
successfully set up.
|
||||
type: int
|
||||
default: 30
|
||||
wait_for_configured:
|
||||
description:
|
||||
- Whether to wait until the firewall has been successfully configured before
|
||||
determining what to do, and before returning from the module.
|
||||
- The API returns status C(in progress) when the firewall is currently
|
||||
being configured. If this happens, the module will try again until
|
||||
the status changes to C(active) or C(disabled).
|
||||
- Please note that there is a request limit. If you have to do multiple
|
||||
updates, it can be better to disable waiting, and regularly use
|
||||
M(hetzner_firewall_info) to query status.
|
||||
type: bool
|
||||
default: yes
|
||||
wait_delay:
|
||||
description:
|
||||
- Delay to wait (in seconds) before checking again whether the firewall has
|
||||
been configured.
|
||||
type: int
|
||||
default: 10
|
||||
timeout:
|
||||
description:
|
||||
- Timeout (in seconds) for waiting for firewall to be configured.
|
||||
type: int
|
||||
default: 180
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Configure firewall for server with main IP 1.2.3.4
|
||||
hetzner_firewall:
|
||||
hetzner_user: foo
|
||||
hetzner_password: bar
|
||||
server_ip: 1.2.3.4
|
||||
status: active
|
||||
whitelist_hos: yes
|
||||
rules:
|
||||
input:
|
||||
- name: Allow everything to ports 20-23 from 4.3.2.1/24
|
||||
ip_version: ipv4
|
||||
src_ip: 4.3.2.1/24
|
||||
dst_port: '20-23'
|
||||
action: accept
|
||||
- name: Allow everything to port 443
|
||||
ip_version: ipv4
|
||||
dst_port: '443'
|
||||
action: accept
|
||||
- name: Drop everything else
|
||||
ip_version: ipv4
|
||||
action: discard
|
||||
register: result
|
||||
|
||||
- debug:
|
||||
msg: "{{ result }}"
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
firewall:
|
||||
description:
|
||||
- The firewall configuration.
|
||||
type: dict
|
||||
returned: success
|
||||
contains:
|
||||
port:
|
||||
description:
|
||||
- Switch port of firewall.
|
||||
- C(main) or C(kvm).
|
||||
type: str
|
||||
sample: main
|
||||
server_ip:
|
||||
description:
|
||||
- Server's main IP address.
|
||||
type: str
|
||||
sample: 1.2.3.4
|
||||
server_number:
|
||||
description:
|
||||
- Hetzner's internal server number.
|
||||
type: int
|
||||
sample: 12345
|
||||
status:
|
||||
description:
|
||||
- Status of the firewall.
|
||||
- C(active) or C(disabled).
|
||||
- Will be C(in process) if the firewall is currently updated, and
|
||||
I(wait_for_configured) is set to C(no) or I(timeout) to a too small value.
|
||||
type: str
|
||||
sample: active
|
||||
whitelist_hos:
|
||||
description:
|
||||
- Whether Hetzner services have access.
|
||||
type: bool
|
||||
sample: true
|
||||
rules:
|
||||
description:
|
||||
- Firewall rules.
|
||||
type: dict
|
||||
contains:
|
||||
input:
|
||||
description:
|
||||
- Input firewall rules.
|
||||
type: list
|
||||
elements: dict
|
||||
contains:
|
||||
name:
|
||||
description:
|
||||
- Name of the firewall rule.
|
||||
type: str
|
||||
sample: Allow HTTP access to server
|
||||
ip_version:
|
||||
description:
|
||||
- Internet protocol version.
|
||||
type: str
|
||||
sample: ipv4
|
||||
dst_ip:
|
||||
description:
|
||||
- Destination IP address or subnet address.
|
||||
- CIDR notation.
|
||||
type: str
|
||||
sample: 1.2.3.4/32
|
||||
dst_port:
|
||||
description:
|
||||
- Destination port or port range.
|
||||
type: str
|
||||
sample: "443"
|
||||
src_ip:
|
||||
description:
|
||||
- Source IP address or subnet address.
|
||||
- CIDR notation.
|
||||
type: str
|
||||
sample: null
|
||||
src_port:
|
||||
description:
|
||||
- Source port or port range.
|
||||
type: str
|
||||
sample: null
|
||||
protocol:
|
||||
description:
|
||||
- Protocol above IP layer
|
||||
type: str
|
||||
sample: tcp
|
||||
tcp_flags:
|
||||
description:
|
||||
- TCP flags or logical combination of flags.
|
||||
type: str
|
||||
sample: null
|
||||
action:
|
||||
description:
|
||||
- Action if rule matches.
|
||||
- C(accept) or C(discard).
|
||||
type: str
|
||||
sample: accept
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible_collections.ansible.netcommon.plugins.module_utils.compat import ipaddress as compat_ipaddress
|
||||
from ansible_collections.community.general.plugins.module_utils.hetzner import (
|
||||
HETZNER_DEFAULT_ARGUMENT_SPEC,
|
||||
BASE_URL,
|
||||
fetch_url_json,
|
||||
fetch_url_json_with_retries,
|
||||
CheckDoneTimeoutException,
|
||||
)
|
||||
from ansible.module_utils.six.moves.urllib.parse import urlencode
|
||||
from ansible.module_utils._text import to_native, to_text
|
||||
|
||||
|
||||
RULE_OPTION_NAMES = [
|
||||
'name', 'ip_version', 'dst_ip', 'dst_port', 'src_ip', 'src_port',
|
||||
'protocol', 'tcp_flags', 'action',
|
||||
]
|
||||
|
||||
RULES = ['input']
|
||||
|
||||
|
||||
def restrict_dict(dictionary, fields):
|
||||
result = dict()
|
||||
for k, v in dictionary.items():
|
||||
if k in fields:
|
||||
result[k] = v
|
||||
return result
|
||||
|
||||
|
||||
def restrict_firewall_config(config):
|
||||
result = restrict_dict(config, ['port', 'status', 'whitelist_hos'])
|
||||
result['rules'] = dict()
|
||||
for ruleset in RULES:
|
||||
result['rules'][ruleset] = [
|
||||
restrict_dict(rule, RULE_OPTION_NAMES)
|
||||
for rule in config['rules'].get(ruleset) or []
|
||||
]
|
||||
return result
|
||||
|
||||
|
||||
def update(before, after, params, name):
|
||||
bv = before.get(name)
|
||||
after[name] = bv
|
||||
changed = False
|
||||
pv = params[name]
|
||||
if pv is not None:
|
||||
changed = pv != bv
|
||||
if changed:
|
||||
after[name] = pv
|
||||
return changed
|
||||
|
||||
|
||||
def normalize_ip(ip, ip_version):
|
||||
if ip is None:
|
||||
return ip
|
||||
if '/' in ip:
|
||||
ip, range = ip.split('/')
|
||||
else:
|
||||
ip, range = ip, ''
|
||||
ip_addr = to_native(compat_ipaddress.ip_address(to_text(ip)).compressed)
|
||||
if range == '':
|
||||
range = '32' if ip_version.lower() == 'ipv4' else '128'
|
||||
return ip_addr + '/' + range
|
||||
|
||||
|
||||
def update_rules(before, after, params, ruleset):
|
||||
before_rules = before['rules'][ruleset]
|
||||
after_rules = after['rules'][ruleset]
|
||||
params_rules = params['rules'][ruleset]
|
||||
changed = len(before_rules) != len(params_rules)
|
||||
for no, rule in enumerate(params_rules):
|
||||
rule['src_ip'] = normalize_ip(rule['src_ip'], rule['ip_version'])
|
||||
rule['dst_ip'] = normalize_ip(rule['dst_ip'], rule['ip_version'])
|
||||
if no < len(before_rules):
|
||||
before_rule = before_rules[no]
|
||||
before_rule['src_ip'] = normalize_ip(before_rule['src_ip'], before_rule['ip_version'])
|
||||
before_rule['dst_ip'] = normalize_ip(before_rule['dst_ip'], before_rule['ip_version'])
|
||||
if before_rule != rule:
|
||||
changed = True
|
||||
after_rules.append(rule)
|
||||
return changed
|
||||
|
||||
|
||||
def encode_rule(output, rulename, input):
|
||||
for i, rule in enumerate(input['rules'][rulename]):
|
||||
for k, v in rule.items():
|
||||
if v is not None:
|
||||
output['rules[{0}][{1}][{2}]'.format(rulename, i, k)] = v
|
||||
|
||||
|
||||
def create_default_rules_object():
|
||||
rules = dict()
|
||||
for ruleset in RULES:
|
||||
rules[ruleset] = []
|
||||
return rules
|
||||
|
||||
|
||||
def firewall_configured(result, error):
|
||||
return result['firewall']['status'] != 'in process'
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = dict(
|
||||
server_ip=dict(type='str', required=True),
|
||||
port=dict(type='str', default='main', choices=['main', 'kvm']),
|
||||
state=dict(type='str', default='present', choices=['present', 'absent']),
|
||||
whitelist_hos=dict(type='bool'),
|
||||
rules=dict(type='dict', options=dict(
|
||||
input=dict(type='list', elements='dict', options=dict(
|
||||
name=dict(type='str'),
|
||||
ip_version=dict(type='str', required=True, choices=['ipv4', 'ipv6']),
|
||||
dst_ip=dict(type='str'),
|
||||
dst_port=dict(type='str'),
|
||||
src_ip=dict(type='str'),
|
||||
src_port=dict(type='str'),
|
||||
protocol=dict(type='str'),
|
||||
tcp_flags=dict(type='str'),
|
||||
action=dict(type='str', required=True, choices=['accept', 'discard']),
|
||||
)),
|
||||
)),
|
||||
update_timeout=dict(type='int', default=30),
|
||||
wait_for_configured=dict(type='bool', default=True),
|
||||
wait_delay=dict(type='int', default=10),
|
||||
timeout=dict(type='int', default=180),
|
||||
)
|
||||
argument_spec.update(HETZNER_DEFAULT_ARGUMENT_SPEC)
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
# Sanitize input
|
||||
module.params['status'] = 'active' if (module.params['state'] == 'present') else 'disabled'
|
||||
if module.params['rules'] is None:
|
||||
module.params['rules'] = {}
|
||||
if module.params['rules'].get('input') is None:
|
||||
module.params['rules']['input'] = []
|
||||
|
||||
server_ip = module.params['server_ip']
|
||||
|
||||
# https://robot.your-server.de/doc/webservice/en.html#get-firewall-server-ip
|
||||
url = "{0}/firewall/{1}".format(BASE_URL, server_ip)
|
||||
if module.params['wait_for_configured']:
|
||||
try:
|
||||
result, error = fetch_url_json_with_retries(
|
||||
module,
|
||||
url,
|
||||
check_done_callback=firewall_configured,
|
||||
check_done_delay=module.params['wait_delay'],
|
||||
check_done_timeout=module.params['timeout'],
|
||||
)
|
||||
except CheckDoneTimeoutException as dummy:
|
||||
module.fail_json(msg='Timeout while waiting for firewall to be configured.')
|
||||
else:
|
||||
result, error = fetch_url_json(module, url)
|
||||
if not firewall_configured(result, error):
|
||||
module.fail_json(msg='Firewall configuration cannot be read as it is not configured.')
|
||||
|
||||
full_before = result['firewall']
|
||||
if not full_before.get('rules'):
|
||||
full_before['rules'] = create_default_rules_object()
|
||||
before = restrict_firewall_config(full_before)
|
||||
|
||||
# Build wanted (after) state and compare
|
||||
after = dict(before)
|
||||
changed = False
|
||||
changed |= update(before, after, module.params, 'port')
|
||||
changed |= update(before, after, module.params, 'status')
|
||||
changed |= update(before, after, module.params, 'whitelist_hos')
|
||||
after['rules'] = create_default_rules_object()
|
||||
if module.params['status'] == 'active':
|
||||
for ruleset in RULES:
|
||||
changed |= update_rules(before, after, module.params, ruleset)
|
||||
|
||||
# Update if different
|
||||
construct_result = True
|
||||
construct_status = None
|
||||
if changed and not module.check_mode:
|
||||
# https://robot.your-server.de/doc/webservice/en.html#post-firewall-server-ip
|
||||
url = "{0}/firewall/{1}".format(BASE_URL, server_ip)
|
||||
headers = {"Content-type": "application/x-www-form-urlencoded"}
|
||||
data = dict(after)
|
||||
data['whitelist_hos'] = str(data['whitelist_hos']).lower()
|
||||
del data['rules']
|
||||
for ruleset in RULES:
|
||||
encode_rule(data, ruleset, after)
|
||||
result, error = fetch_url_json(
|
||||
module,
|
||||
url,
|
||||
method='POST',
|
||||
timeout=module.params['update_timeout'],
|
||||
data=urlencode(data),
|
||||
headers=headers,
|
||||
)
|
||||
if module.params['wait_for_configured'] and not firewall_configured(result, error):
|
||||
try:
|
||||
result, error = fetch_url_json_with_retries(
|
||||
module,
|
||||
url,
|
||||
check_done_callback=firewall_configured,
|
||||
check_done_delay=module.params['wait_delay'],
|
||||
check_done_timeout=module.params['timeout'],
|
||||
skip_first=True,
|
||||
)
|
||||
except CheckDoneTimeoutException as e:
|
||||
result, error = e.result, e.error
|
||||
module.warn('Timeout while waiting for firewall to be configured.')
|
||||
|
||||
full_after = result['firewall']
|
||||
if not full_after.get('rules'):
|
||||
full_after['rules'] = create_default_rules_object()
|
||||
construct_status = full_after['status']
|
||||
if construct_status != 'in process':
|
||||
# Only use result if configuration is done, so that diff will be ok
|
||||
after = restrict_firewall_config(full_after)
|
||||
construct_result = False
|
||||
|
||||
if construct_result:
|
||||
# Construct result (used for check mode, and configuration still in process)
|
||||
full_after = dict(full_before)
|
||||
for k, v in after.items():
|
||||
if k != 'rules':
|
||||
full_after[k] = after[k]
|
||||
if construct_status is not None:
|
||||
# We want 'in process' here
|
||||
full_after['status'] = construct_status
|
||||
full_after['rules'] = dict()
|
||||
for ruleset in RULES:
|
||||
full_after['rules'][ruleset] = after['rules'][ruleset]
|
||||
|
||||
module.exit_json(
|
||||
changed=changed,
|
||||
diff=dict(
|
||||
before=before,
|
||||
after=after,
|
||||
),
|
||||
firewall=full_after,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
230
plugins/modules/net_tools/hetzner_firewall_info.py
Normal file
230
plugins/modules/net_tools/hetzner_firewall_info.py
Normal file
@@ -0,0 +1,230 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2019 Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: hetzner_firewall_info
|
||||
short_description: Manage Hetzner's dedicated server firewall
|
||||
author:
|
||||
- Felix Fontein (@felixfontein)
|
||||
description:
|
||||
- Manage Hetzner's dedicated server firewall.
|
||||
seealso:
|
||||
- name: Firewall documentation
|
||||
description: Hetzner's documentation on the stateless firewall for dedicated servers
|
||||
link: https://wiki.hetzner.de/index.php/Robot_Firewall/en
|
||||
- module: hetzner_firewall
|
||||
description: Configure firewall.
|
||||
extends_documentation_fragment:
|
||||
- community.general.hetzner
|
||||
|
||||
options:
|
||||
server_ip:
|
||||
description: The server's main IP address.
|
||||
type: str
|
||||
required: yes
|
||||
wait_for_configured:
|
||||
description:
|
||||
- Whether to wait until the firewall has been successfully configured before
|
||||
determining what to do, and before returning from the module.
|
||||
- The API returns status C(in progress) when the firewall is currently
|
||||
being configured. If this happens, the module will try again until
|
||||
the status changes to C(active) or C(disabled).
|
||||
- Please note that there is a request limit. If you have to do multiple
|
||||
updates, it can be better to disable waiting, and regularly use
|
||||
M(hetzner_firewall_info) to query status.
|
||||
type: bool
|
||||
default: yes
|
||||
wait_delay:
|
||||
description:
|
||||
- Delay to wait (in seconds) before checking again whether the firewall has
|
||||
been configured.
|
||||
type: int
|
||||
default: 10
|
||||
timeout:
|
||||
description:
|
||||
- Timeout (in seconds) for waiting for firewall to be configured.
|
||||
type: int
|
||||
default: 180
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Get firewall configuration for server with main IP 1.2.3.4
|
||||
hetzner_firewall_info:
|
||||
hetzner_user: foo
|
||||
hetzner_password: bar
|
||||
server_ip: 1.2.3.4
|
||||
register: result
|
||||
|
||||
- debug:
|
||||
msg: "{{ result.firewall }}"
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
firewall:
|
||||
description:
|
||||
- The firewall configuration.
|
||||
type: dict
|
||||
returned: success
|
||||
contains:
|
||||
port:
|
||||
description:
|
||||
- Switch port of firewall.
|
||||
- C(main) or C(kvm).
|
||||
type: str
|
||||
sample: main
|
||||
server_ip:
|
||||
description:
|
||||
- Server's main IP address.
|
||||
type: str
|
||||
sample: 1.2.3.4
|
||||
server_number:
|
||||
description:
|
||||
- Hetzner's internal server number.
|
||||
type: int
|
||||
sample: 12345
|
||||
status:
|
||||
description:
|
||||
- Status of the firewall.
|
||||
- C(active) or C(disabled).
|
||||
- Will be C(in process) if the firewall is currently updated, and
|
||||
I(wait_for_configured) is set to C(no) or I(timeout) to a too small value.
|
||||
type: str
|
||||
sample: active
|
||||
whitelist_hos:
|
||||
description:
|
||||
- Whether Hetzner services have access.
|
||||
type: bool
|
||||
sample: true
|
||||
rules:
|
||||
description:
|
||||
- Firewall rules.
|
||||
type: dict
|
||||
contains:
|
||||
input:
|
||||
description:
|
||||
- Input firewall rules.
|
||||
type: list
|
||||
elements: dict
|
||||
contains:
|
||||
name:
|
||||
description:
|
||||
- Name of the firewall rule.
|
||||
type: str
|
||||
sample: Allow HTTP access to server
|
||||
ip_version:
|
||||
description:
|
||||
- Internet protocol version.
|
||||
type: str
|
||||
sample: ipv4
|
||||
dst_ip:
|
||||
description:
|
||||
- Destination IP address or subnet address.
|
||||
- CIDR notation.
|
||||
type: str
|
||||
sample: 1.2.3.4/32
|
||||
dst_port:
|
||||
description:
|
||||
- Destination port or port range.
|
||||
type: str
|
||||
sample: "443"
|
||||
src_ip:
|
||||
description:
|
||||
- Source IP address or subnet address.
|
||||
- CIDR notation.
|
||||
type: str
|
||||
sample: null
|
||||
src_port:
|
||||
description:
|
||||
- Source port or port range.
|
||||
type: str
|
||||
sample: null
|
||||
protocol:
|
||||
description:
|
||||
- Protocol above IP layer
|
||||
type: str
|
||||
sample: tcp
|
||||
tcp_flags:
|
||||
description:
|
||||
- TCP flags or logical combination of flags.
|
||||
type: str
|
||||
sample: null
|
||||
action:
|
||||
description:
|
||||
- Action if rule matches.
|
||||
- C(accept) or C(discard).
|
||||
type: str
|
||||
sample: accept
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible_collections.community.general.plugins.module_utils.hetzner import (
|
||||
HETZNER_DEFAULT_ARGUMENT_SPEC,
|
||||
BASE_URL,
|
||||
fetch_url_json,
|
||||
fetch_url_json_with_retries,
|
||||
CheckDoneTimeoutException,
|
||||
)
|
||||
|
||||
|
||||
def firewall_configured(result, error):
|
||||
return result['firewall']['status'] != 'in process'
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = dict(
|
||||
server_ip=dict(type='str', required=True),
|
||||
wait_for_configured=dict(type='bool', default=True),
|
||||
wait_delay=dict(type='int', default=10),
|
||||
timeout=dict(type='int', default=180),
|
||||
)
|
||||
argument_spec.update(HETZNER_DEFAULT_ARGUMENT_SPEC)
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
server_ip = module.params['server_ip']
|
||||
|
||||
# https://robot.your-server.de/doc/webservice/en.html#get-firewall-server-ip
|
||||
url = "{0}/firewall/{1}".format(BASE_URL, server_ip)
|
||||
if module.params['wait_for_configured']:
|
||||
try:
|
||||
result, error = fetch_url_json_with_retries(
|
||||
module,
|
||||
url,
|
||||
check_done_callback=firewall_configured,
|
||||
check_done_delay=module.params['wait_delay'],
|
||||
check_done_timeout=module.params['timeout'],
|
||||
)
|
||||
except CheckDoneTimeoutException as dummy:
|
||||
module.fail_json(msg='Timeout while waiting for firewall to be configured.')
|
||||
else:
|
||||
result, error = fetch_url_json(module, url)
|
||||
|
||||
firewall = result['firewall']
|
||||
if not firewall.get('rules'):
|
||||
firewall['rules'] = dict()
|
||||
for ruleset in ['input']:
|
||||
firewall['rules'][ruleset] = []
|
||||
|
||||
module.exit_json(
|
||||
changed=False,
|
||||
firewall=firewall,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
569
plugins/modules/net_tools/infinity/infinity.py
Normal file
569
plugins/modules/net_tools/infinity/infinity.py
Normal file
@@ -0,0 +1,569 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2017, <meiliu@fusionlayer.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
module: infinity
|
||||
short_description: Manage Infinity IPAM using Rest API
|
||||
description:
|
||||
- Manage Infinity IPAM using REST API.
|
||||
author:
|
||||
- Meirong Liu (@MeganLiu)
|
||||
options:
|
||||
server_ip:
|
||||
description:
|
||||
- Infinity server_ip with IP address.
|
||||
type: str
|
||||
required: true
|
||||
username:
|
||||
description:
|
||||
- Username to access Infinity.
|
||||
- The user must have REST API privileges.
|
||||
type: str
|
||||
required: true
|
||||
password:
|
||||
description:
|
||||
- Infinity password.
|
||||
type: str
|
||||
required: true
|
||||
action:
|
||||
description:
|
||||
- Action to perform
|
||||
type: str
|
||||
required: true
|
||||
choices: [add_network, delete_network, get_network, get_network_id, release_ip, release_network, reserve_network, reserve_next_available_ip ]
|
||||
network_id:
|
||||
description:
|
||||
- Network ID.
|
||||
type: str
|
||||
default: ''
|
||||
ip_address:
|
||||
description:
|
||||
- IP Address for a reservation or a release.
|
||||
type: str
|
||||
default: ''
|
||||
network_address:
|
||||
description:
|
||||
- Network address with CIDR format (e.g., 192.168.310.0).
|
||||
type: str
|
||||
default: ''
|
||||
network_size:
|
||||
description:
|
||||
- Network bitmask (e.g. 255.255.255.220) or CIDR format (e.g., /26).
|
||||
type: str
|
||||
default: ''
|
||||
network_name:
|
||||
description:
|
||||
- The name of a network.
|
||||
type: str
|
||||
default: ''
|
||||
network_location:
|
||||
description:
|
||||
- The parent network id for a given network.
|
||||
type: int
|
||||
default: -1
|
||||
network_type:
|
||||
description:
|
||||
- Network type defined by Infinity
|
||||
type: str
|
||||
choices: [ lan, shared_lan, supernet ]
|
||||
default: lan
|
||||
network_family:
|
||||
description:
|
||||
- Network family defined by Infinity, e.g. IPv4, IPv6 and Dual stack
|
||||
type: str
|
||||
choices: [ 4, 6, dual ]
|
||||
default: 4
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
---
|
||||
- hosts: localhost
|
||||
connection: local
|
||||
strategy: debug
|
||||
tasks:
|
||||
- name: Reserve network into Infinity IPAM
|
||||
infinity:
|
||||
server_ip: 80.75.107.12
|
||||
username: username
|
||||
password: password
|
||||
action: reserve_network
|
||||
network_name: reserve_new_ansible_network
|
||||
network_family: 4
|
||||
network_type: lan
|
||||
network_id: 1201
|
||||
network_size: /28
|
||||
register: infinity
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
network_id:
|
||||
description: id for a given network
|
||||
returned: success
|
||||
type: str
|
||||
sample: '1501'
|
||||
ip_info:
|
||||
description: when reserve next available ip address from a network, the ip address info ) is returned.
|
||||
returned: success
|
||||
type: str
|
||||
sample: '{"address": "192.168.10.3", "hostname": "", "FQDN": "", "domainname": "", "id": 3229}'
|
||||
network_info:
|
||||
description: when reserving a LAN network from a Infinity supernet by providing network_size, the information about the reserved network is returned.
|
||||
returned: success
|
||||
type: str
|
||||
sample: {"network_address": "192.168.10.32/28","network_family": "4", "network_id": 3102,
|
||||
"network_size": null,"description": null,"network_location": "3085",
|
||||
"ranges": { "id": 0, "name": null,"first_ip": null,"type": null,"last_ip": null},
|
||||
"network_type": "lan","network_name": "'reserve_new_ansible_network'"}
|
||||
'''
|
||||
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, json
|
||||
from ansible.module_utils.urls import open_url
|
||||
|
||||
|
||||
class Infinity(object):
|
||||
"""
|
||||
Class for manage REST API calls with the Infinity.
|
||||
"""
|
||||
|
||||
def __init__(self, module, server_ip, username, password):
|
||||
self.module = module
|
||||
self.auth_user = username
|
||||
self.auth_pass = password
|
||||
self.base_url = "https://%s/rest/v1/" % (str(server_ip))
|
||||
|
||||
def _get_api_call_ansible_handler(
|
||||
self,
|
||||
method='get',
|
||||
resource_url='',
|
||||
stat_codes=None,
|
||||
params=None,
|
||||
payload_data=None):
|
||||
"""
|
||||
Perform the HTTPS request by using ansible get/delete method
|
||||
"""
|
||||
stat_codes = [200] if stat_codes is None else stat_codes
|
||||
request_url = str(self.base_url) + str(resource_url)
|
||||
response = None
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
if not request_url:
|
||||
self.module.exit_json(
|
||||
msg="When sending Rest api call , the resource URL is empty, please check.")
|
||||
if payload_data and not isinstance(payload_data, str):
|
||||
payload_data = json.dumps(payload_data)
|
||||
response_raw = open_url(
|
||||
str(request_url),
|
||||
method=method,
|
||||
timeout=20,
|
||||
headers=headers,
|
||||
url_username=self.auth_user,
|
||||
url_password=self.auth_pass,
|
||||
validate_certs=False,
|
||||
force_basic_auth=True,
|
||||
data=payload_data)
|
||||
|
||||
response = response_raw.read()
|
||||
payload = ''
|
||||
if response_raw.code not in stat_codes:
|
||||
self.module.exit_json(
|
||||
changed=False,
|
||||
meta=" openurl response_raw.code show error and error code is %r" %
|
||||
(response_raw.code))
|
||||
else:
|
||||
if isinstance(response, str) and len(response) > 0:
|
||||
payload = response
|
||||
elif method.lower() == 'delete' and response_raw.code == 204:
|
||||
payload = 'Delete is done.'
|
||||
if isinstance(payload, dict) and "text" in payload:
|
||||
self.module.exit_json(
|
||||
changed=False,
|
||||
meta="when calling rest api, returned data is not json ")
|
||||
raise Exception(payload["text"])
|
||||
return payload
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# get_network()
|
||||
# ---------------------------------------------------------------------------
|
||||
def get_network(self, network_id, network_name, limit=-1):
|
||||
"""
|
||||
Search network_name inside Infinity by using rest api
|
||||
Network id or network_name needs to be provided
|
||||
return the details of a given with given network_id or name
|
||||
"""
|
||||
if network_name is None and network_id is None:
|
||||
self.module.exit_json(
|
||||
msg="You must specify one of the options 'network_name' or 'network_id'.")
|
||||
method = "get"
|
||||
resource_url = ''
|
||||
params = {}
|
||||
response = None
|
||||
if network_id:
|
||||
resource_url = "networks/" + str(network_id)
|
||||
response = self._get_api_call_ansible_handler(method, resource_url)
|
||||
if network_id is None and network_name:
|
||||
method = "get"
|
||||
resource_url = "search"
|
||||
params = {"query": json.dumps(
|
||||
{"name": network_name, "type": "network"})}
|
||||
response = self._get_api_call_ansible_handler(
|
||||
method, resource_url, payload_data=json.dumps(params))
|
||||
if response and isinstance(response, str):
|
||||
response = json.loads(response)
|
||||
if response and isinstance(response, list) and len(
|
||||
response) > 1 and limit == 1:
|
||||
response = response[0]
|
||||
response = json.dumps(response)
|
||||
return response
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# get_network_id()
|
||||
# ---------------------------------------------------------------------------
|
||||
def get_network_id(self, network_name="", network_type='lan'):
|
||||
"""
|
||||
query network_id from Infinity via rest api based on given network_name
|
||||
"""
|
||||
method = 'get'
|
||||
resource_url = 'search'
|
||||
response = None
|
||||
if network_name is None:
|
||||
self.module.exit_json(
|
||||
msg="You must specify the option 'network_name'")
|
||||
params = {"query": json.dumps(
|
||||
{"name": network_name, "type": "network"})}
|
||||
response = self._get_api_call_ansible_handler(
|
||||
method, resource_url, payload_data=json.dumps(params))
|
||||
network_id = ""
|
||||
if response and isinstance(response, str):
|
||||
response = json.loads(response)
|
||||
if response and isinstance(response, list):
|
||||
response = response[0]
|
||||
network_id = response['id']
|
||||
return network_id
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# reserve_next_available_ip()
|
||||
# ---------------------------------------------------------------------------
|
||||
def reserve_next_available_ip(self, network_id=""):
|
||||
"""
|
||||
Reserve ip address via Infinity by using rest api
|
||||
network_id: the id of the network that users would like to reserve network from
|
||||
return the next available ip address from that given network
|
||||
"""
|
||||
method = "post"
|
||||
resource_url = ''
|
||||
response = None
|
||||
ip_info = ''
|
||||
if not network_id:
|
||||
self.module.exit_json(
|
||||
msg="You must specify the option 'network_id'.")
|
||||
if network_id:
|
||||
resource_url = "networks/" + str(network_id) + "/reserve_ip"
|
||||
response = self._get_api_call_ansible_handler(method, resource_url)
|
||||
if response and response.find(
|
||||
"[") >= 0 and response.find("]") >= 0:
|
||||
start_pos = response.find("{")
|
||||
end_pos = response.find("}")
|
||||
ip_info = response[start_pos: (end_pos + 1)]
|
||||
return ip_info
|
||||
|
||||
# -------------------------
|
||||
# release_ip()
|
||||
# -------------------------
|
||||
def release_ip(self, network_id="", ip_address=""):
|
||||
"""
|
||||
Reserve ip address via Infinity by using rest api
|
||||
"""
|
||||
method = "get"
|
||||
resource_url = ''
|
||||
response = None
|
||||
if ip_address is None or network_id is None:
|
||||
self.module.exit_json(
|
||||
msg="You must specify those two options: 'network_id' and 'ip_address'.")
|
||||
|
||||
resource_url = "networks/" + str(network_id) + "/children"
|
||||
response = self._get_api_call_ansible_handler(method, resource_url)
|
||||
if not response:
|
||||
self.module.exit_json(
|
||||
msg="There is an error in release ip %s from network %s." %
|
||||
(ip_address, network_id))
|
||||
|
||||
ip_list = json.loads(response)
|
||||
ip_idlist = []
|
||||
for ip_item in ip_list:
|
||||
ip_id = ip_item['id']
|
||||
ip_idlist.append(ip_id)
|
||||
deleted_ip_id = ''
|
||||
for ip_id in ip_idlist:
|
||||
ip_response = ''
|
||||
resource_url = "ip_addresses/" + str(ip_id)
|
||||
ip_response = self._get_api_call_ansible_handler(
|
||||
method,
|
||||
resource_url,
|
||||
stat_codes=[200])
|
||||
if ip_response and json.loads(
|
||||
ip_response)['address'] == str(ip_address):
|
||||
deleted_ip_id = ip_id
|
||||
break
|
||||
if deleted_ip_id:
|
||||
method = 'delete'
|
||||
resource_url = "ip_addresses/" + str(deleted_ip_id)
|
||||
response = self._get_api_call_ansible_handler(
|
||||
method, resource_url, stat_codes=[204])
|
||||
else:
|
||||
self.module.exit_json(
|
||||
msg=" When release ip, could not find the ip address %r from the given network %r' ." %
|
||||
(ip_address, network_id))
|
||||
|
||||
return response
|
||||
|
||||
# -------------------
|
||||
# delete_network()
|
||||
# -------------------
|
||||
def delete_network(self, network_id="", network_name=""):
|
||||
"""
|
||||
delete network from Infinity by using rest api
|
||||
"""
|
||||
method = 'delete'
|
||||
resource_url = ''
|
||||
response = None
|
||||
if network_id is None and network_name is None:
|
||||
self.module.exit_json(
|
||||
msg="You must specify one of those options: 'network_id','network_name' .")
|
||||
if network_id is None and network_name:
|
||||
network_id = self.get_network_id(network_name=network_name)
|
||||
if network_id:
|
||||
resource_url = "networks/" + str(network_id)
|
||||
response = self._get_api_call_ansible_handler(
|
||||
method, resource_url, stat_codes=[204])
|
||||
return response
|
||||
|
||||
# reserve_network()
|
||||
# ---------------------------------------------------------------------------
|
||||
def reserve_network(self, network_id="",
|
||||
reserved_network_name="", reserved_network_description="",
|
||||
reserved_network_size="", reserved_network_family='4',
|
||||
reserved_network_type='lan', reserved_network_address="",):
|
||||
"""
|
||||
Reserves the first available network of specified size from a given supernet
|
||||
<dt>network_name (required)</dt><dd>Name of the network</dd>
|
||||
<dt>description (optional)</dt><dd>Free description</dd>
|
||||
<dt>network_family (required)</dt><dd>Address family of the network. One of '4', '6', 'IPv4', 'IPv6', 'dual'</dd>
|
||||
<dt>network_address (optional)</dt><dd>Address of the new network. If not given, the first network available will be created.</dd>
|
||||
<dt>network_size (required)</dt><dd>Size of the new network in /<prefix> notation.</dd>
|
||||
<dt>network_type (required)</dt><dd>Type of network. One of 'supernet', 'lan', 'shared_lan'</dd>
|
||||
|
||||
"""
|
||||
method = 'post'
|
||||
resource_url = ''
|
||||
network_info = None
|
||||
if network_id is None or reserved_network_name is None or reserved_network_size is None:
|
||||
self.module.exit_json(
|
||||
msg="You must specify those options: 'network_id', 'reserved_network_name' and 'reserved_network_size'")
|
||||
if network_id:
|
||||
resource_url = "networks/" + str(network_id) + "/reserve_network"
|
||||
if not reserved_network_family:
|
||||
reserved_network_family = '4'
|
||||
if not reserved_network_type:
|
||||
reserved_network_type = 'lan'
|
||||
payload_data = {
|
||||
"network_name": reserved_network_name,
|
||||
'description': reserved_network_description,
|
||||
'network_size': reserved_network_size,
|
||||
'network_family': reserved_network_family,
|
||||
'network_type': reserved_network_type,
|
||||
'network_location': int(network_id)}
|
||||
if reserved_network_address:
|
||||
payload_data.update({'network_address': reserved_network_address})
|
||||
|
||||
network_info = self._get_api_call_ansible_handler(
|
||||
method, resource_url, stat_codes=[200, 201], payload_data=payload_data)
|
||||
|
||||
return network_info
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# release_network()
|
||||
# ---------------------------------------------------------------------------
|
||||
def release_network(
|
||||
self,
|
||||
network_id="",
|
||||
released_network_name="",
|
||||
released_network_type='lan'):
|
||||
"""
|
||||
Release the network with name 'released_network_name' from the given supernet network_id
|
||||
"""
|
||||
method = 'get'
|
||||
response = None
|
||||
if network_id is None or released_network_name is None:
|
||||
self.module.exit_json(
|
||||
msg="You must specify those options 'network_id', 'reserved_network_name' and 'reserved_network_size'")
|
||||
matched_network_id = ""
|
||||
resource_url = "networks/" + str(network_id) + "/children"
|
||||
response = self._get_api_call_ansible_handler(method, resource_url)
|
||||
if not response:
|
||||
self.module.exit_json(
|
||||
msg=" there is an error in releasing network %r from network %s." %
|
||||
(network_id, released_network_name))
|
||||
if response:
|
||||
response = json.loads(response)
|
||||
for child_net in response:
|
||||
if child_net['network'] and child_net['network']['network_name'] == released_network_name:
|
||||
matched_network_id = child_net['network']['network_id']
|
||||
break
|
||||
response = None
|
||||
if matched_network_id:
|
||||
method = 'delete'
|
||||
resource_url = "networks/" + str(matched_network_id)
|
||||
response = self._get_api_call_ansible_handler(
|
||||
method, resource_url, stat_codes=[204])
|
||||
else:
|
||||
self.module.exit_json(
|
||||
msg=" When release network , could not find the network %r from the given superent %r' " %
|
||||
(released_network_name, network_id))
|
||||
|
||||
return response
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# add_network()
|
||||
# ---------------------------------------------------------------------------
|
||||
def add_network(
|
||||
self, network_name="", network_address="",
|
||||
network_size="", network_family='4',
|
||||
network_type='lan', network_location=-1):
|
||||
"""
|
||||
add a new LAN network into a given supernet Fusionlayer Infinity via rest api or default supernet
|
||||
required fields=['network_name', 'network_family', 'network_type', 'network_address','network_size' ]
|
||||
"""
|
||||
method = 'post'
|
||||
resource_url = 'networks'
|
||||
response = None
|
||||
if network_name is None or network_address is None or network_size is None:
|
||||
self.module.exit_json(
|
||||
msg="You must specify those options 'network_name', 'network_address' and 'network_size'")
|
||||
|
||||
if not network_family:
|
||||
network_family = '4'
|
||||
if not network_type:
|
||||
network_type = 'lan'
|
||||
if not network_location:
|
||||
network_location = -1
|
||||
payload_data = {
|
||||
"network_name": network_name,
|
||||
'network_address': network_address,
|
||||
'network_size': network_size,
|
||||
'network_family': network_family,
|
||||
'network_type': network_type,
|
||||
'network_location': network_location}
|
||||
response = self._get_api_call_ansible_handler(
|
||||
method='post', resource_url=resource_url,
|
||||
stat_codes=[200], payload_data=payload_data)
|
||||
return response
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
server_ip=dict(type='str', required=True),
|
||||
username=dict(type='str', required=True),
|
||||
password=dict(type='str', required=True, no_log=True),
|
||||
network_id=dict(type='str'),
|
||||
ip_address=dict(type='str'),
|
||||
network_name=dict(type='str'),
|
||||
network_location=dict(type='int', default=-1),
|
||||
network_family=dict(type='str', default='4', choices=['4', '6', 'dual']),
|
||||
network_type=dict(type='str', default='lan', choices=['lan', 'shared_lan', 'supernet']),
|
||||
network_address=dict(type='str'),
|
||||
network_size=dict(type='str'),
|
||||
action=dict(type='str', required=True, choices=[
|
||||
'add_network',
|
||||
'delete_network',
|
||||
'get_network',
|
||||
'get_network_id',
|
||||
'release_ip',
|
||||
'release_network',
|
||||
'reserve_network',
|
||||
'reserve_next_available_ip',
|
||||
],),
|
||||
),
|
||||
required_together=(
|
||||
['username', 'password'],
|
||||
),
|
||||
)
|
||||
server_ip = module.params["server_ip"]
|
||||
username = module.params["username"]
|
||||
password = module.params["password"]
|
||||
action = module.params["action"]
|
||||
network_id = module.params["network_id"]
|
||||
released_ip = module.params["ip_address"]
|
||||
network_name = module.params["network_name"]
|
||||
network_family = module.params["network_family"]
|
||||
network_type = module.params["network_type"]
|
||||
network_address = module.params["network_address"]
|
||||
network_size = module.params["network_size"]
|
||||
network_location = module.params["network_location"]
|
||||
my_infinity = Infinity(module, server_ip, username, password)
|
||||
result = ''
|
||||
if action == "reserve_next_available_ip":
|
||||
if network_id:
|
||||
result = my_infinity.reserve_next_available_ip(network_id)
|
||||
if not result:
|
||||
result = 'There is an error in calling method of reserve_next_available_ip'
|
||||
module.exit_json(changed=False, meta=result)
|
||||
module.exit_json(changed=True, meta=result)
|
||||
elif action == "release_ip":
|
||||
if network_id and released_ip:
|
||||
result = my_infinity.release_ip(
|
||||
network_id=network_id, ip_address=released_ip)
|
||||
module.exit_json(changed=True, meta=result)
|
||||
elif action == "delete_network":
|
||||
result = my_infinity.delete_network(
|
||||
network_id=network_id, network_name=network_name)
|
||||
module.exit_json(changed=True, meta=result)
|
||||
|
||||
elif action == "get_network_id":
|
||||
result = my_infinity.get_network_id(
|
||||
network_name=network_name, network_type=network_type)
|
||||
module.exit_json(changed=True, meta=result)
|
||||
elif action == "get_network":
|
||||
result = my_infinity.get_network(
|
||||
network_id=network_id, network_name=network_name)
|
||||
module.exit_json(changed=True, meta=result)
|
||||
elif action == "reserve_network":
|
||||
result = my_infinity.reserve_network(
|
||||
network_id=network_id,
|
||||
reserved_network_name=network_name,
|
||||
reserved_network_size=network_size,
|
||||
reserved_network_family=network_family,
|
||||
reserved_network_type=network_type,
|
||||
reserved_network_address=network_address)
|
||||
module.exit_json(changed=True, meta=result)
|
||||
elif action == "release_network":
|
||||
result = my_infinity.release_network(
|
||||
network_id=network_id,
|
||||
released_network_name=network_name,
|
||||
released_network_type=network_type)
|
||||
module.exit_json(changed=True, meta=result)
|
||||
|
||||
elif action == "add_network":
|
||||
result = my_infinity.add_network(
|
||||
network_name=network_name,
|
||||
network_location=network_location,
|
||||
network_address=network_address,
|
||||
network_size=network_size,
|
||||
network_family=network_family,
|
||||
network_type=network_type)
|
||||
|
||||
module.exit_json(changed=True, meta=result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
146
plugins/modules/net_tools/ip_netns.py
Normal file
146
plugins/modules/net_tools/ip_netns.py
Normal file
@@ -0,0 +1,146 @@
|
||||
#!/usr/bin/python
|
||||
# (c) 2017, Arie Bregman <abregman@redhat.com>
|
||||
#
|
||||
# This file is a module for Ansible that interacts with Network Manager
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: ip_netns
|
||||
author: "Arie Bregman (@bregman-arie)"
|
||||
short_description: Manage network namespaces
|
||||
requirements: [ ip ]
|
||||
description:
|
||||
- Create or delete network namespaces using the ip command.
|
||||
options:
|
||||
name:
|
||||
required: false
|
||||
description:
|
||||
- Name of the namespace
|
||||
state:
|
||||
required: false
|
||||
default: "present"
|
||||
choices: [ present, absent ]
|
||||
description:
|
||||
- Whether the namespace should exist
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Create a namespace named mario
|
||||
- name: Create a namespace named mario
|
||||
namespace:
|
||||
name: mario
|
||||
state: present
|
||||
- name: Delete a namespace named luigi
|
||||
namespace:
|
||||
name: luigi
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
# Default return values
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils._text import to_text
|
||||
|
||||
|
||||
class Namespace(object):
|
||||
"""Interface to network namespaces. """
|
||||
|
||||
def __init__(self, module):
|
||||
self.module = module
|
||||
self.name = module.params['name']
|
||||
self.state = module.params['state']
|
||||
|
||||
def _netns(self, command):
|
||||
'''Run ip nents command'''
|
||||
return self.module.run_command(['ip', 'netns'] + command)
|
||||
|
||||
def exists(self):
|
||||
'''Check if the namespace already exists'''
|
||||
rc, out, err = self.module.run_command('ip netns list')
|
||||
if rc != 0:
|
||||
self.module.fail_json(msg=to_text(err))
|
||||
return self.name in out
|
||||
|
||||
def add(self):
|
||||
'''Create network namespace'''
|
||||
rtc, out, err = self._netns(['add', self.name])
|
||||
|
||||
if rtc != 0:
|
||||
self.module.fail_json(msg=err)
|
||||
|
||||
def delete(self):
|
||||
'''Delete network namespace'''
|
||||
rtc, out, err = self._netns(['del', self.name])
|
||||
if rtc != 0:
|
||||
self.module.fail_json(msg=err)
|
||||
|
||||
def check(self):
|
||||
'''Run check mode'''
|
||||
changed = False
|
||||
|
||||
if self.state == 'present' and self.exists():
|
||||
changed = True
|
||||
|
||||
elif self.state == 'absent' and self.exists():
|
||||
changed = True
|
||||
elif self.state == 'present' and not self.exists():
|
||||
changed = True
|
||||
|
||||
self.module.exit_json(changed=changed)
|
||||
|
||||
def run(self):
|
||||
'''Make the necessary changes'''
|
||||
changed = False
|
||||
|
||||
if self.state == 'absent':
|
||||
if self.exists():
|
||||
self.delete()
|
||||
changed = True
|
||||
elif self.state == 'present':
|
||||
if not self.exists():
|
||||
self.add()
|
||||
changed = True
|
||||
|
||||
self.module.exit_json(changed=changed)
|
||||
|
||||
|
||||
def main():
|
||||
"""Entry point."""
|
||||
module = AnsibleModule(
|
||||
argument_spec={
|
||||
'name': {'default': None},
|
||||
'state': {'default': 'present', 'choices': ['present', 'absent']},
|
||||
},
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
network_namespace = Namespace(module)
|
||||
if module.check_mode:
|
||||
network_namespace.check()
|
||||
else:
|
||||
network_namespace.run()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
110
plugins/modules/net_tools/ipify_facts.py
Normal file
110
plugins/modules/net_tools/ipify_facts.py
Normal file
@@ -0,0 +1,110 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright: (c) 2015, René Moser <mail@renemoser.net>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: ipify_facts
|
||||
short_description: Retrieve the public IP of your internet gateway
|
||||
description:
|
||||
- If behind NAT and need to know the public IP of your internet gateway.
|
||||
author:
|
||||
- René Moser (@resmo)
|
||||
options:
|
||||
api_url:
|
||||
description:
|
||||
- URL of the ipify.org API service.
|
||||
- C(?format=json) will be appended per default.
|
||||
type: str
|
||||
default: https://api.ipify.org/
|
||||
timeout:
|
||||
description:
|
||||
- HTTP connection timeout in seconds.
|
||||
type: int
|
||||
default: 10
|
||||
validate_certs:
|
||||
description:
|
||||
- When set to C(NO), SSL certificates will not be validated.
|
||||
type: bool
|
||||
default: yes
|
||||
notes:
|
||||
- Visit https://www.ipify.org to get more information.
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
# Gather IP facts from ipify.org
|
||||
- name: Get my public IP
|
||||
ipify_facts:
|
||||
|
||||
# Gather IP facts from your own ipify service endpoint with a custom timeout
|
||||
- name: Get my public IP
|
||||
ipify_facts:
|
||||
api_url: http://api.example.com/ipify
|
||||
timeout: 20
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
---
|
||||
ipify_public_ip:
|
||||
description: Public IP of the internet gateway.
|
||||
returned: success
|
||||
type: str
|
||||
sample: 1.2.3.4
|
||||
'''
|
||||
|
||||
import json
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.urls import fetch_url
|
||||
from ansible.module_utils._text import to_text
|
||||
|
||||
|
||||
class IpifyFacts(object):
|
||||
|
||||
def __init__(self):
|
||||
self.api_url = module.params.get('api_url')
|
||||
self.timeout = module.params.get('timeout')
|
||||
|
||||
def run(self):
|
||||
result = {
|
||||
'ipify_public_ip': None
|
||||
}
|
||||
(response, info) = fetch_url(module=module, url=self.api_url + "?format=json", force=True, timeout=self.timeout)
|
||||
|
||||
if not response:
|
||||
module.fail_json(msg="No valid or no response from url %s within %s seconds (timeout)" % (self.api_url, self.timeout))
|
||||
|
||||
data = json.loads(to_text(response.read()))
|
||||
result['ipify_public_ip'] = data.get('ip')
|
||||
return result
|
||||
|
||||
|
||||
def main():
|
||||
global module
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
api_url=dict(type='str', default='https://api.ipify.org/'),
|
||||
timeout=dict(type='int', default=10),
|
||||
validate_certs=dict(type='bool', default=True),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
ipify_facts = IpifyFacts().run()
|
||||
ipify_facts_result = dict(changed=False, ansible_facts=ipify_facts)
|
||||
module.exit_json(**ipify_facts_result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
134
plugins/modules/net_tools/ipinfoio_facts.py
Normal file
134
plugins/modules/net_tools/ipinfoio_facts.py
Normal file
@@ -0,0 +1,134 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: UTF-8 -*-
|
||||
|
||||
# Copyright: (c) 2016, Aleksei Kostiuk <unitoff@gmail.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: ipinfoio_facts
|
||||
short_description: "Retrieve IP geolocation facts of a host's IP address"
|
||||
description:
|
||||
- "Gather IP geolocation facts of a host's IP address using ipinfo.io API"
|
||||
author: "Aleksei Kostiuk (@akostyuk)"
|
||||
options:
|
||||
timeout:
|
||||
description:
|
||||
- HTTP connection timeout in seconds
|
||||
required: false
|
||||
default: 10
|
||||
http_agent:
|
||||
description:
|
||||
- Set http user agent
|
||||
required: false
|
||||
default: "ansible-ipinfoio-module/0.0.1"
|
||||
notes:
|
||||
- "Check http://ipinfo.io/ for more information"
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Retrieve geolocation data of a host's IP address
|
||||
- name: get IP geolocation data
|
||||
ipinfoio_facts:
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
ansible_facts:
|
||||
description: "Dictionary of ip geolocation facts for a host's IP address"
|
||||
returned: changed
|
||||
type: complex
|
||||
contains:
|
||||
ip:
|
||||
description: "Public IP address of a host"
|
||||
type: str
|
||||
sample: "8.8.8.8"
|
||||
hostname:
|
||||
description: Domain name
|
||||
type: str
|
||||
sample: "google-public-dns-a.google.com"
|
||||
country:
|
||||
description: ISO 3166-1 alpha-2 country code
|
||||
type: str
|
||||
sample: "US"
|
||||
region:
|
||||
description: State or province name
|
||||
type: str
|
||||
sample: "California"
|
||||
city:
|
||||
description: City name
|
||||
type: str
|
||||
sample: "Mountain View"
|
||||
loc:
|
||||
description: Latitude and Longitude of the location
|
||||
type: str
|
||||
sample: "37.3860,-122.0838"
|
||||
org:
|
||||
description: "organization's name"
|
||||
type: str
|
||||
sample: "AS3356 Level 3 Communications, Inc."
|
||||
postal:
|
||||
description: Postal code
|
||||
type: str
|
||||
sample: "94035"
|
||||
'''
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
from ansible.module_utils.urls import fetch_url
|
||||
|
||||
|
||||
USER_AGENT = 'ansible-ipinfoio-module/0.0.1'
|
||||
|
||||
|
||||
class IpinfoioFacts(object):
|
||||
|
||||
def __init__(self, module):
|
||||
self.url = 'https://ipinfo.io/json'
|
||||
self.timeout = module.params.get('timeout')
|
||||
self.module = module
|
||||
|
||||
def get_geo_data(self):
|
||||
response, info = fetch_url(self.module, self.url, force=True, # NOQA
|
||||
timeout=self.timeout)
|
||||
try:
|
||||
info['status'] == 200
|
||||
except AssertionError:
|
||||
self.module.fail_json(msg='Could not get {0} page, '
|
||||
'check for connectivity!'.format(self.url))
|
||||
else:
|
||||
try:
|
||||
content = response.read()
|
||||
result = self.module.from_json(content.decode('utf8'))
|
||||
except ValueError:
|
||||
self.module.fail_json(
|
||||
msg='Failed to parse the ipinfo.io response: '
|
||||
'{0} {1}'.format(self.url, content))
|
||||
else:
|
||||
return result
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule( # NOQA
|
||||
argument_spec=dict(
|
||||
http_agent=dict(default=USER_AGENT),
|
||||
timeout=dict(type='int', default=10),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
ipinfoio = IpinfoioFacts(module)
|
||||
ipinfoio_result = dict(
|
||||
changed=False, ansible_facts=ipinfoio.get_geo_data())
|
||||
module.exit_json(**ipinfoio_result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
362
plugins/modules/net_tools/ipwcli_dns.py
Normal file
362
plugins/modules/net_tools/ipwcli_dns.py
Normal file
@@ -0,0 +1,362 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
# Copyright: (c) 2020, Christian Wollinger <cwollinger@web.de>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {
|
||||
'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'
|
||||
}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: ipwcli_dns
|
||||
|
||||
short_description: Manage DNS Records for Ericsson IPWorks via ipwcli
|
||||
|
||||
|
||||
description:
|
||||
- "Manage DNS records for the Ericsson IPWorks DNS server. The module will use the ipwcli to deploy the DNS records."
|
||||
|
||||
requirements:
|
||||
- ipwcli (installed on Ericsson IPWorks)
|
||||
|
||||
notes:
|
||||
- To make the DNS record changes effective, you need to run C(update dnsserver) on the ipwcli.
|
||||
|
||||
options:
|
||||
dnsname:
|
||||
description:
|
||||
- Name of the record.
|
||||
required: true
|
||||
type: str
|
||||
type:
|
||||
description:
|
||||
- Type of the record.
|
||||
required: true
|
||||
type: str
|
||||
choices: [ NAPTR, SRV, A, AAAA ]
|
||||
container:
|
||||
description:
|
||||
- Sets the container zone for the record.
|
||||
required: true
|
||||
type: str
|
||||
address:
|
||||
description:
|
||||
- The IP address for the A or AAAA record.
|
||||
- Required for C(type=A) or C(type=AAAA)
|
||||
type: str
|
||||
ttl:
|
||||
description:
|
||||
- Sets the TTL of the record.
|
||||
type: int
|
||||
default: 3600
|
||||
state:
|
||||
description:
|
||||
- Whether the record should exist or not.
|
||||
type: str
|
||||
choices: [ absent, present ]
|
||||
default: present
|
||||
priority:
|
||||
description:
|
||||
- Sets the priority of the SRV record.
|
||||
type: int
|
||||
default: 10
|
||||
weight:
|
||||
description:
|
||||
- Sets the weight of the SRV record.
|
||||
type: int
|
||||
default: 10
|
||||
port:
|
||||
description:
|
||||
- Sets the port of the SRV record.
|
||||
- Required for C(type=SRV)
|
||||
type: int
|
||||
target:
|
||||
description:
|
||||
- Sets the target of the SRV record.
|
||||
- Required for C(type=SRV)
|
||||
type: str
|
||||
order:
|
||||
description:
|
||||
- Sets the order of the NAPTR record.
|
||||
- Required for C(type=NAPTR)
|
||||
type: int
|
||||
preference:
|
||||
description:
|
||||
- Sets the preference of the NAPTR record.
|
||||
- Required for C(type=NAPTR)
|
||||
type: int
|
||||
flags:
|
||||
description:
|
||||
- Sets one of the possible flags of NAPTR record.
|
||||
- Required for C(type=NAPTR)
|
||||
type: str
|
||||
choices: ['S', 'A', 'U', 'P']
|
||||
service:
|
||||
description:
|
||||
- Sets the service of the NAPTR record.
|
||||
- Required for C(type=NAPTR)
|
||||
type: str
|
||||
replacement:
|
||||
description:
|
||||
- Sets the replacement of the NAPTR record.
|
||||
- Required for C(type=NAPTR)
|
||||
type: str
|
||||
username:
|
||||
description:
|
||||
- Username to login on ipwcli.
|
||||
type: str
|
||||
required: true
|
||||
password:
|
||||
description:
|
||||
- Password to login on ipwcli.
|
||||
type: str
|
||||
required: true
|
||||
|
||||
author:
|
||||
- Christian Wollinger (@cwollinger)
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Create A record
|
||||
ipwcli_dns:
|
||||
dnsname: example.com
|
||||
type: A
|
||||
container: ZoneOne
|
||||
address: 127.0.0.1
|
||||
|
||||
- name: Remove SRV record if exists
|
||||
ipwcli_dns:
|
||||
dnsname: _sip._tcp.test.example.com
|
||||
type: SRV
|
||||
container: ZoneOne
|
||||
ttl: 100
|
||||
state: absent
|
||||
target: example.com
|
||||
port: 5060
|
||||
|
||||
- name: Create NAPTR record
|
||||
ipwcli_dns:
|
||||
dnsname: test.example.com
|
||||
type: NAPTR
|
||||
preference: 10
|
||||
container: ZoneOne
|
||||
ttl: 100
|
||||
order: 10
|
||||
service: 'SIP+D2T'
|
||||
replacement: '_sip._tcp.test.example.com.'
|
||||
flags: S
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
record:
|
||||
description: The created record from the input params
|
||||
type: str
|
||||
returned: always
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
import os
|
||||
|
||||
|
||||
class ResourceRecord(object):
|
||||
|
||||
def __init__(self, module):
|
||||
self.module = module
|
||||
self.dnsname = module.params['dnsname']
|
||||
self.dnstype = module.params['type']
|
||||
self.container = module.params['container']
|
||||
self.address = module.params['address']
|
||||
self.ttl = module.params['ttl']
|
||||
self.state = module.params['state']
|
||||
self.priority = module.params['priority']
|
||||
self.weight = module.params['weight']
|
||||
self.port = module.params['port']
|
||||
self.target = module.params['target']
|
||||
self.order = module.params['order']
|
||||
self.preference = module.params['preference']
|
||||
self.flags = module.params['flags']
|
||||
self.service = module.params['service']
|
||||
self.replacement = module.params['replacement']
|
||||
self.user = module.params['username']
|
||||
self.password = module.params['password']
|
||||
|
||||
def create_naptrrecord(self):
|
||||
# create NAPTR record with the given params
|
||||
if not self.preference:
|
||||
self.module.fail_json(msg='missing required arguments for NAPTR record: preference')
|
||||
|
||||
if not self.order:
|
||||
self.module.fail_json(msg='missing required arguments for NAPTR record: order')
|
||||
|
||||
if not self.service:
|
||||
self.module.fail_json(msg='missing required arguments for NAPTR record: service')
|
||||
|
||||
if not self.replacement:
|
||||
self.module.fail_json(msg='missing required arguments for NAPTR record: replacement')
|
||||
|
||||
record = ('naptrrecord %s -set ttl=%s;container=%s;order=%s;preference=%s;flags="%s";service="%s";replacement="%s"'
|
||||
% (self.dnsname, self.ttl, self.container, self.order, self.preference, self.flags, self.service, self.replacement))
|
||||
return record
|
||||
|
||||
def create_srvrecord(self):
|
||||
# create SRV record with the given params
|
||||
if not self.port:
|
||||
self.module.fail_json(msg='missing required arguments for SRV record: port')
|
||||
|
||||
if not self.target:
|
||||
self.module.fail_json(msg='missing required arguments for SRV record: target')
|
||||
|
||||
record = ('srvrecord %s -set ttl=%s;container=%s;priority=%s;weight=%s;port=%s;target=%s'
|
||||
% (self.dnsname, self.ttl, self.container, self.priority, self.weight, self.port, self.target))
|
||||
return record
|
||||
|
||||
def create_arecord(self):
|
||||
# create A record with the given params
|
||||
if not self.address:
|
||||
self.module.fail_json(msg='missing required arguments for A record: address')
|
||||
|
||||
if self.dnstype == 'AAAA':
|
||||
record = 'aaaarecord %s %s -set ttl=%s;container=%s' % (self.dnsname, self.address, self.ttl, self.container)
|
||||
else:
|
||||
record = 'arecord %s %s -set ttl=%s;container=%s' % (self.dnsname, self.address, self.ttl, self.container)
|
||||
|
||||
return record
|
||||
|
||||
def list_record(self, record):
|
||||
# check if the record exists via list on ipwcli
|
||||
search = 'list %s' % (record.replace(';', '&&').replace('set', 'where'))
|
||||
cmd = [self.module.get_bin_path('ipwcli', True)]
|
||||
cmd.append('-user=%s' % (self.user))
|
||||
cmd.append('-password=%s' % (self.password))
|
||||
rc, out, err = self.module.run_command(cmd, data=search)
|
||||
|
||||
if 'Invalid username or password' in out:
|
||||
self.module.fail_json(msg='access denied at ipwcli login: Invalid username or password')
|
||||
|
||||
if (('ARecord %s' % self.dnsname in out and rc == 0) or ('SRVRecord %s' % self.dnsname in out and rc == 0) or
|
||||
('NAPTRRecord %s' % self.dnsname in out and rc == 0)):
|
||||
return True, rc, out, err
|
||||
|
||||
return False, rc, out, err
|
||||
|
||||
def deploy_record(self, record):
|
||||
# check what happens if create fails on ipworks
|
||||
stdin = 'create %s' % (record)
|
||||
cmd = [self.module.get_bin_path('ipwcli', True)]
|
||||
cmd.append('-user=%s' % (self.user))
|
||||
cmd.append('-password=%s' % (self.password))
|
||||
rc, out, err = self.module.run_command(cmd, data=stdin)
|
||||
|
||||
if 'Invalid username or password' in out:
|
||||
self.module.fail_json(msg='access denied at ipwcli login: Invalid username or password')
|
||||
|
||||
if '1 object(s) created.' in out:
|
||||
return rc, out, err
|
||||
else:
|
||||
self.module.fail_json(msg='record creation failed', stderr=out)
|
||||
|
||||
def delete_record(self, record):
|
||||
# check what happens if create fails on ipworks
|
||||
stdin = 'delete %s' % (record.replace(';', '&&').replace('set', 'where'))
|
||||
cmd = [self.module.get_bin_path('ipwcli', True)]
|
||||
cmd.append('-user=%s' % (self.user))
|
||||
cmd.append('-password=%s' % (self.password))
|
||||
rc, out, err = self.module.run_command(cmd, data=stdin)
|
||||
|
||||
if 'Invalid username or password' in out:
|
||||
self.module.fail_json(msg='access denied at ipwcli login: Invalid username or password')
|
||||
|
||||
if '1 object(s) were updated.' in out:
|
||||
return rc, out, err
|
||||
else:
|
||||
self.module.fail_json(msg='record deletion failed', stderr=out)
|
||||
|
||||
|
||||
def run_module():
|
||||
# define available arguments/parameters a user can pass to the module
|
||||
module_args = dict(
|
||||
dnsname=dict(type='str', required=True),
|
||||
type=dict(type='str', required=True, choices=['A', 'AAAA', 'SRV', 'NAPTR']),
|
||||
container=dict(type='str', required=True),
|
||||
address=dict(type='str', required=False),
|
||||
ttl=dict(type='int', required=False, default=3600),
|
||||
state=dict(type='str', default='present', choices=['absent', 'present']),
|
||||
priority=dict(type='int', required=False, default=10),
|
||||
weight=dict(type='int', required=False, default=10),
|
||||
port=dict(type='int', required=False),
|
||||
target=dict(type='str', required=False),
|
||||
order=dict(type='int', required=False),
|
||||
preference=dict(type='int', required=False),
|
||||
flags=dict(type='str', required=False, choices=['S', 'A', 'U', 'P']),
|
||||
service=dict(type='str', required=False),
|
||||
replacement=dict(type='str', required=False),
|
||||
username=dict(type='str', required=True),
|
||||
password=dict(type='str', required=True, no_log=True)
|
||||
)
|
||||
|
||||
# define result
|
||||
result = dict(
|
||||
changed=False,
|
||||
stdout='',
|
||||
stderr='',
|
||||
rc=0,
|
||||
record=''
|
||||
)
|
||||
|
||||
# supports check mode
|
||||
module = AnsibleModule(
|
||||
argument_spec=module_args,
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
user = ResourceRecord(module)
|
||||
|
||||
if user.dnstype == 'NAPTR':
|
||||
record = user.create_naptrrecord()
|
||||
elif user.dnstype == 'SRV':
|
||||
record = user.create_srvrecord()
|
||||
elif user.dnstype == 'A' or user.dnstype == 'AAAA':
|
||||
record = user.create_arecord()
|
||||
|
||||
found, rc, out, err = user.list_record(record)
|
||||
|
||||
if found and user.state == 'absent':
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
rc, out, err = user.delete_record(record)
|
||||
result['changed'] = True
|
||||
result['record'] = record
|
||||
result['rc'] = rc
|
||||
result['stdout'] = out
|
||||
result['stderr'] = err
|
||||
elif not found and user.state == 'present':
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
rc, out, err = user.deploy_record(record)
|
||||
result['changed'] = True
|
||||
result['record'] = record
|
||||
result['rc'] = rc
|
||||
result['stdout'] = out
|
||||
result['stderr'] = err
|
||||
else:
|
||||
result['changed'] = False
|
||||
result['record'] = record
|
||||
result['rc'] = rc
|
||||
result['stdout'] = out
|
||||
result['stderr'] = err
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
def main():
|
||||
run_module()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
292
plugins/modules/net_tools/ldap/ldap_attr.py
Normal file
292
plugins/modules/net_tools/ldap/ldap_attr.py
Normal file
@@ -0,0 +1,292 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2016, Peter Sagerson <psagers@ignorare.net>
|
||||
# Copyright: (c) 2016, Jiri Tyr <jiri.tyr@gmail.com>
|
||||
# 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': ['deprecated'],
|
||||
'supported_by': 'community'
|
||||
}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: ldap_attr
|
||||
short_description: Add or remove LDAP attribute values
|
||||
description:
|
||||
- Add or remove LDAP attribute values.
|
||||
notes:
|
||||
- This only deals with attributes on existing entries. To add or remove
|
||||
whole entries, see M(ldap_entry).
|
||||
- The default authentication settings will attempt to use a SASL EXTERNAL
|
||||
bind over a UNIX domain socket. This works well with the default Ubuntu
|
||||
install for example, which includes a cn=peercred,cn=external,cn=auth ACL
|
||||
rule allowing root to modify the server configuration. If you need to use
|
||||
a simple bind to access your server, pass the credentials in I(bind_dn)
|
||||
and I(bind_pw).
|
||||
- For I(state=present) and I(state=absent), all value comparisons are
|
||||
performed on the server for maximum accuracy. For I(state=exact), values
|
||||
have to be compared in Python, which obviously ignores LDAP matching
|
||||
rules. This should work out in most cases, but it is theoretically
|
||||
possible to see spurious changes when target and actual values are
|
||||
semantically identical but lexically distinct.
|
||||
deprecated:
|
||||
removed_in: '2.14'
|
||||
why: 'The current "ldap_attr" module does not support LDAP attribute insertions or deletions with objectClass dependencies.'
|
||||
alternative: 'Use M(ldap_attrs) instead. Deprecated in 2.10.'
|
||||
author:
|
||||
- Jiri Tyr (@jtyr)
|
||||
requirements:
|
||||
- python-ldap
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- The name of the attribute to modify.
|
||||
type: str
|
||||
required: true
|
||||
state:
|
||||
description:
|
||||
- The state of the attribute values.
|
||||
- If C(present), all given values will be added if they're missing.
|
||||
- If C(absent), all given values will be removed if present.
|
||||
- If C(exact), the set of values will be forced to exactly those provided and no others.
|
||||
- If I(state=exact) and I(value) is an empty list, all values for this attribute will be removed.
|
||||
type: str
|
||||
choices: [ absent, exact, present ]
|
||||
default: present
|
||||
values:
|
||||
description:
|
||||
- The value(s) to add or remove. This can be a string or a list of
|
||||
strings. The complex argument format is required in order to pass
|
||||
a list of strings (see examples).
|
||||
type: raw
|
||||
required: true
|
||||
params:
|
||||
description:
|
||||
- Additional module parameters.
|
||||
type: dict
|
||||
extends_documentation_fragment:
|
||||
- community.general.ldap.documentation
|
||||
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Configure directory number 1 for example.com
|
||||
ldap_attr:
|
||||
dn: olcDatabase={1}hdb,cn=config
|
||||
name: olcSuffix
|
||||
values: dc=example,dc=com
|
||||
state: exact
|
||||
|
||||
# The complex argument format is required here to pass a list of ACL strings.
|
||||
- name: Set up the ACL
|
||||
ldap_attr:
|
||||
dn: olcDatabase={1}hdb,cn=config
|
||||
name: olcAccess
|
||||
values:
|
||||
- >-
|
||||
{0}to attrs=userPassword,shadowLastChange
|
||||
by self write
|
||||
by anonymous auth
|
||||
by dn="cn=admin,dc=example,dc=com" write
|
||||
by * none'
|
||||
- >-
|
||||
{1}to dn.base="dc=example,dc=com"
|
||||
by dn="cn=admin,dc=example,dc=com" write
|
||||
by * read
|
||||
state: exact
|
||||
|
||||
- name: Declare some indexes
|
||||
ldap_attr:
|
||||
dn: olcDatabase={1}hdb,cn=config
|
||||
name: olcDbIndex
|
||||
values: "{{ item }}"
|
||||
with_items:
|
||||
- objectClass eq
|
||||
- uid eq
|
||||
|
||||
- name: Set up a root user, which we can use later to bootstrap the directory
|
||||
ldap_attr:
|
||||
dn: olcDatabase={1}hdb,cn=config
|
||||
name: "{{ item.key }}"
|
||||
values: "{{ item.value }}"
|
||||
state: exact
|
||||
with_dict:
|
||||
olcRootDN: cn=root,dc=example,dc=com
|
||||
olcRootPW: "{SSHA}tabyipcHzhwESzRaGA7oQ/SDoBZQOGND"
|
||||
|
||||
- name: Get rid of an unneeded attribute
|
||||
ldap_attr:
|
||||
dn: uid=jdoe,ou=people,dc=example,dc=com
|
||||
name: shadowExpire
|
||||
values: []
|
||||
state: exact
|
||||
server_uri: ldap://localhost/
|
||||
bind_dn: cn=admin,dc=example,dc=com
|
||||
bind_pw: password
|
||||
|
||||
#
|
||||
# The same as in the previous example but with the authentication details
|
||||
# stored in the ldap_auth variable:
|
||||
#
|
||||
# ldap_auth:
|
||||
# server_uri: ldap://localhost/
|
||||
# bind_dn: cn=admin,dc=example,dc=com
|
||||
# bind_pw: password
|
||||
- name: Get rid of an unneeded attribute
|
||||
ldap_attr:
|
||||
dn: uid=jdoe,ou=people,dc=example,dc=com
|
||||
name: shadowExpire
|
||||
values: []
|
||||
state: exact
|
||||
params: "{{ ldap_auth }}"
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
modlist:
|
||||
description: list of modified parameters
|
||||
returned: success
|
||||
type: list
|
||||
sample: '[[2, "olcRootDN", ["cn=root,dc=example,dc=com"]]]'
|
||||
'''
|
||||
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
from ansible.module_utils._text import to_native, to_bytes
|
||||
from ansible_collections.community.general.plugins.module_utils.ldap import LdapGeneric, gen_specs
|
||||
|
||||
LDAP_IMP_ERR = None
|
||||
try:
|
||||
import ldap
|
||||
|
||||
HAS_LDAP = True
|
||||
except ImportError:
|
||||
LDAP_IMP_ERR = traceback.format_exc()
|
||||
HAS_LDAP = False
|
||||
|
||||
|
||||
class LdapAttr(LdapGeneric):
|
||||
def __init__(self, module):
|
||||
LdapGeneric.__init__(self, module)
|
||||
|
||||
# Shortcuts
|
||||
self.name = self.module.params['name']
|
||||
self.state = self.module.params['state']
|
||||
|
||||
# Normalize values
|
||||
if isinstance(self.module.params['values'], list):
|
||||
self.values = list(map(to_bytes, self.module.params['values']))
|
||||
else:
|
||||
self.values = [to_bytes(self.module.params['values'])]
|
||||
|
||||
def add(self):
|
||||
values_to_add = list(filter(self._is_value_absent, self.values))
|
||||
|
||||
if len(values_to_add) > 0:
|
||||
modlist = [(ldap.MOD_ADD, self.name, values_to_add)]
|
||||
else:
|
||||
modlist = []
|
||||
|
||||
return modlist
|
||||
|
||||
def delete(self):
|
||||
values_to_delete = list(filter(self._is_value_present, self.values))
|
||||
|
||||
if len(values_to_delete) > 0:
|
||||
modlist = [(ldap.MOD_DELETE, self.name, values_to_delete)]
|
||||
else:
|
||||
modlist = []
|
||||
|
||||
return modlist
|
||||
|
||||
def exact(self):
|
||||
try:
|
||||
results = self.connection.search_s(
|
||||
self.dn, ldap.SCOPE_BASE, attrlist=[self.name])
|
||||
except ldap.LDAPError as e:
|
||||
self.fail("Cannot search for attribute %s" % self.name, e)
|
||||
|
||||
current = results[0][1].get(self.name, [])
|
||||
modlist = []
|
||||
|
||||
if frozenset(self.values) != frozenset(current):
|
||||
if len(current) == 0:
|
||||
modlist = [(ldap.MOD_ADD, self.name, self.values)]
|
||||
elif len(self.values) == 0:
|
||||
modlist = [(ldap.MOD_DELETE, self.name, None)]
|
||||
else:
|
||||
modlist = [(ldap.MOD_REPLACE, self.name, self.values)]
|
||||
|
||||
return modlist
|
||||
|
||||
def _is_value_present(self, value):
|
||||
""" True if the target attribute has the given value. """
|
||||
try:
|
||||
is_present = bool(
|
||||
self.connection.compare_s(self.dn, self.name, value))
|
||||
except ldap.NO_SUCH_ATTRIBUTE:
|
||||
is_present = False
|
||||
|
||||
return is_present
|
||||
|
||||
def _is_value_absent(self, value):
|
||||
""" True if the target attribute doesn't have the given value. """
|
||||
return not self._is_value_present(value)
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=gen_specs(
|
||||
name=dict(type='str', required=True),
|
||||
params=dict(type='dict'),
|
||||
state=dict(type='str', default='present', choices=['absent', 'exact', 'present']),
|
||||
values=dict(type='raw', required=True),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
if not HAS_LDAP:
|
||||
module.fail_json(msg=missing_required_lib('python-ldap'),
|
||||
exception=LDAP_IMP_ERR)
|
||||
|
||||
# Update module parameters with user's parameters if defined
|
||||
if 'params' in module.params and isinstance(module.params['params'], dict):
|
||||
module.params.update(module.params['params'])
|
||||
# Remove the params
|
||||
module.params.pop('params', None)
|
||||
|
||||
# Instantiate the LdapAttr object
|
||||
ldap = LdapAttr(module)
|
||||
|
||||
state = module.params['state']
|
||||
|
||||
# Perform action
|
||||
if state == 'present':
|
||||
modlist = ldap.add()
|
||||
elif state == 'absent':
|
||||
modlist = ldap.delete()
|
||||
elif state == 'exact':
|
||||
modlist = ldap.exact()
|
||||
|
||||
changed = False
|
||||
|
||||
if len(modlist) > 0:
|
||||
changed = True
|
||||
|
||||
if not module.check_mode:
|
||||
try:
|
||||
ldap.connection.modify_s(ldap.dn, modlist)
|
||||
except Exception as e:
|
||||
module.fail_json(msg="Attribute action failed.", details=to_native(e))
|
||||
|
||||
module.exit_json(changed=changed, modlist=modlist)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
324
plugins/modules/net_tools/ldap/ldap_attrs.py
Normal file
324
plugins/modules/net_tools/ldap/ldap_attrs.py
Normal file
@@ -0,0 +1,324 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2019, Maciej Delmanowski <drybjed@gmail.com>
|
||||
# Copyright: (c) 2017, Alexander Korinek <noles@a3k.net>
|
||||
# Copyright: (c) 2016, Peter Sagerson <psagers@ignorare.net>
|
||||
# Copyright: (c) 2016, Jiri Tyr <jiri.tyr@gmail.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {
|
||||
'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'
|
||||
}
|
||||
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: ldap_attrs
|
||||
short_description: Add or remove multiple LDAP attribute values
|
||||
description:
|
||||
- Add or remove multiple LDAP attribute values.
|
||||
notes:
|
||||
- This only deals with attributes on existing entries. To add or remove
|
||||
whole entries, see M(ldap_entry).
|
||||
- The default authentication settings will attempt to use a SASL EXTERNAL
|
||||
bind over a UNIX domain socket. This works well with the default Ubuntu
|
||||
install for example, which includes a cn=peercred,cn=external,cn=auth ACL
|
||||
rule allowing root to modify the server configuration. If you need to use
|
||||
a simple bind to access your server, pass the credentials in I(bind_dn)
|
||||
and I(bind_pw).
|
||||
- For I(state=present) and I(state=absent), all value comparisons are
|
||||
performed on the server for maximum accuracy. For I(state=exact), values
|
||||
have to be compared in Python, which obviously ignores LDAP matching
|
||||
rules. This should work out in most cases, but it is theoretically
|
||||
possible to see spurious changes when target and actual values are
|
||||
semantically identical but lexically distinct.
|
||||
author:
|
||||
- Jiri Tyr (@jtyr)
|
||||
- Alexander Korinek (@noles)
|
||||
- Maciej Delmanowski (@drybjed)
|
||||
requirements:
|
||||
- python-ldap
|
||||
options:
|
||||
state:
|
||||
required: false
|
||||
type: str
|
||||
choices: [present, absent, exact]
|
||||
default: present
|
||||
description:
|
||||
- The state of the attribute values. If C(present), all given attribute
|
||||
values will be added if they're missing. If C(absent), all given
|
||||
attribute values will be removed if present. If C(exact), the set of
|
||||
attribute values will be forced to exactly those provided and no others.
|
||||
If I(state=exact) and the attribute I(value) is empty, all values for
|
||||
this attribute will be removed.
|
||||
attributes:
|
||||
required: true
|
||||
type: dict
|
||||
description:
|
||||
- The attribute(s) and value(s) to add or remove. The complex argument format is required in order to pass
|
||||
a list of strings (see examples).
|
||||
ordered:
|
||||
required: false
|
||||
type: bool
|
||||
default: 'no'
|
||||
description:
|
||||
- If C(yes), prepend list values with X-ORDERED index numbers in all
|
||||
attributes specified in the current task. This is useful mostly with
|
||||
I(olcAccess) attribute to easily manage LDAP Access Control Lists.
|
||||
extends_documentation_fragment:
|
||||
- community.general.ldap.documentation
|
||||
|
||||
'''
|
||||
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Configure directory number 1 for example.com
|
||||
ldap_attrs:
|
||||
dn: olcDatabase={1}hdb,cn=config
|
||||
attributes:
|
||||
olcSuffix: dc=example,dc=com
|
||||
state: exact
|
||||
|
||||
# The complex argument format is required here to pass a list of ACL strings.
|
||||
- name: Set up the ACL
|
||||
ldap_attrs:
|
||||
dn: olcDatabase={1}hdb,cn=config
|
||||
attributes:
|
||||
olcAccess:
|
||||
- >-
|
||||
{0}to attrs=userPassword,shadowLastChange
|
||||
by self write
|
||||
by anonymous auth
|
||||
by dn="cn=admin,dc=example,dc=com" write
|
||||
by * none'
|
||||
- >-
|
||||
{1}to dn.base="dc=example,dc=com"
|
||||
by dn="cn=admin,dc=example,dc=com" write
|
||||
by * read
|
||||
state: exact
|
||||
|
||||
# An alternative approach with automatic X-ORDERED numbering
|
||||
- name: Set up the ACL
|
||||
ldap_attrs:
|
||||
dn: olcDatabase={1}hdb,cn=config
|
||||
attributes:
|
||||
olcAccess:
|
||||
- >-
|
||||
to attrs=userPassword,shadowLastChange
|
||||
by self write
|
||||
by anonymous auth
|
||||
by dn="cn=admin,dc=example,dc=com" write
|
||||
by * none'
|
||||
- >-
|
||||
to dn.base="dc=example,dc=com"
|
||||
by dn="cn=admin,dc=example,dc=com" write
|
||||
by * read
|
||||
ordered: yes
|
||||
state: exact
|
||||
|
||||
- name: Declare some indexes
|
||||
ldap_attrs:
|
||||
dn: olcDatabase={1}hdb,cn=config
|
||||
attributes:
|
||||
olcDbIndex:
|
||||
- objectClass eq
|
||||
- uid eq
|
||||
|
||||
- name: Set up a root user, which we can use later to bootstrap the directory
|
||||
ldap_attrs:
|
||||
dn: olcDatabase={1}hdb,cn=config
|
||||
attributes:
|
||||
olcRootDN: cn=root,dc=example,dc=com
|
||||
olcRootPW: "{SSHA}tabyipcHzhwESzRaGA7oQ/SDoBZQOGND"
|
||||
state: exact
|
||||
|
||||
- name: Remove an attribute with a specific value
|
||||
ldap_attrs:
|
||||
dn: uid=jdoe,ou=people,dc=example,dc=com
|
||||
attributes:
|
||||
description: "An example user account"
|
||||
state: absent
|
||||
server_uri: ldap://localhost/
|
||||
bind_dn: cn=admin,dc=example,dc=com
|
||||
bind_pw: password
|
||||
|
||||
- name: Remove specified attribute(s) from an entry
|
||||
ldap_attrs:
|
||||
dn: uid=jdoe,ou=people,dc=example,dc=com
|
||||
attributes:
|
||||
description: []
|
||||
state: exact
|
||||
server_uri: ldap://localhost/
|
||||
bind_dn: cn=admin,dc=example,dc=com
|
||||
bind_pw: password
|
||||
'''
|
||||
|
||||
|
||||
RETURN = r'''
|
||||
modlist:
|
||||
description: list of modified parameters
|
||||
returned: success
|
||||
type: list
|
||||
sample: '[[2, "olcRootDN", ["cn=root,dc=example,dc=com"]]]'
|
||||
'''
|
||||
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
from ansible.module_utils._text import to_native, to_bytes
|
||||
from ansible_collections.community.general.plugins.module_utils.ldap import LdapGeneric, gen_specs
|
||||
import re
|
||||
|
||||
LDAP_IMP_ERR = None
|
||||
try:
|
||||
import ldap
|
||||
|
||||
HAS_LDAP = True
|
||||
except ImportError:
|
||||
LDAP_IMP_ERR = traceback.format_exc()
|
||||
HAS_LDAP = False
|
||||
|
||||
|
||||
class LdapAttrs(LdapGeneric):
|
||||
def __init__(self, module):
|
||||
LdapGeneric.__init__(self, module)
|
||||
|
||||
# Shortcuts
|
||||
self.attrs = self.module.params['attributes']
|
||||
self.state = self.module.params['state']
|
||||
self.ordered = self.module.params['ordered']
|
||||
|
||||
def _order_values(self, values):
|
||||
""" Preprend X-ORDERED index numbers to attribute's values. """
|
||||
ordered_values = []
|
||||
|
||||
if isinstance(values, list):
|
||||
for index, value in enumerate(values):
|
||||
cleaned_value = re.sub(r'^\{\d+\}', '', value)
|
||||
ordered_values.append('{' + str(index) + '}' + cleaned_value)
|
||||
|
||||
return ordered_values
|
||||
|
||||
def _normalize_values(self, values):
|
||||
""" Normalize attribute's values. """
|
||||
norm_values = []
|
||||
|
||||
if isinstance(values, list):
|
||||
if self.ordered:
|
||||
norm_values = list(map(to_bytes,
|
||||
self._order_values(list(map(str,
|
||||
values)))))
|
||||
else:
|
||||
norm_values = list(map(to_bytes, values))
|
||||
else:
|
||||
norm_values = [to_bytes(str(values))]
|
||||
|
||||
return norm_values
|
||||
|
||||
def add(self):
|
||||
modlist = []
|
||||
for name, values in self.module.params['attributes'].items():
|
||||
norm_values = self._normalize_values(values)
|
||||
for value in norm_values:
|
||||
if self._is_value_absent(name, value):
|
||||
modlist.append((ldap.MOD_ADD, name, value))
|
||||
|
||||
return modlist
|
||||
|
||||
def delete(self):
|
||||
modlist = []
|
||||
for name, values in self.module.params['attributes'].items():
|
||||
norm_values = self._normalize_values(values)
|
||||
for value in norm_values:
|
||||
if self._is_value_present(name, value):
|
||||
modlist.append((ldap.MOD_DELETE, name, value))
|
||||
|
||||
return modlist
|
||||
|
||||
def exact(self):
|
||||
modlist = []
|
||||
for name, values in self.module.params['attributes'].items():
|
||||
norm_values = self._normalize_values(values)
|
||||
try:
|
||||
results = self.connection.search_s(
|
||||
self.dn, ldap.SCOPE_BASE, attrlist=[name])
|
||||
except ldap.LDAPError as e:
|
||||
self.fail("Cannot search for attribute %s" % name, e)
|
||||
|
||||
current = results[0][1].get(name, [])
|
||||
|
||||
if frozenset(norm_values) != frozenset(current):
|
||||
if len(current) == 0:
|
||||
modlist.append((ldap.MOD_ADD, name, norm_values))
|
||||
elif len(norm_values) == 0:
|
||||
modlist.append((ldap.MOD_DELETE, name, None))
|
||||
else:
|
||||
modlist.append((ldap.MOD_REPLACE, name, norm_values))
|
||||
|
||||
return modlist
|
||||
|
||||
def _is_value_present(self, name, value):
|
||||
""" True if the target attribute has the given value. """
|
||||
try:
|
||||
is_present = bool(
|
||||
self.connection.compare_s(self.dn, name, value))
|
||||
except ldap.NO_SUCH_ATTRIBUTE:
|
||||
is_present = False
|
||||
|
||||
return is_present
|
||||
|
||||
def _is_value_absent(self, name, value):
|
||||
""" True if the target attribute doesn't have the given value. """
|
||||
return not self._is_value_present(name, value)
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=gen_specs(
|
||||
attributes=dict(type='dict', required=True),
|
||||
ordered=dict(type='bool', default=False, required=False),
|
||||
state=dict(type='str', default='present', choices=['absent', 'exact', 'present']),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
if not HAS_LDAP:
|
||||
module.fail_json(msg=missing_required_lib('python-ldap'),
|
||||
exception=LDAP_IMP_ERR)
|
||||
|
||||
# Instantiate the LdapAttr object
|
||||
ldap = LdapAttrs(module)
|
||||
|
||||
state = module.params['state']
|
||||
|
||||
# Perform action
|
||||
if state == 'present':
|
||||
modlist = ldap.add()
|
||||
elif state == 'absent':
|
||||
modlist = ldap.delete()
|
||||
elif state == 'exact':
|
||||
modlist = ldap.exact()
|
||||
|
||||
changed = False
|
||||
|
||||
if len(modlist) > 0:
|
||||
changed = True
|
||||
|
||||
if not module.check_mode:
|
||||
try:
|
||||
ldap.connection.modify_s(ldap.dn, modlist)
|
||||
except Exception as e:
|
||||
module.fail_json(msg="Attribute action failed.", details=to_native(e))
|
||||
|
||||
module.exit_json(changed=changed, modlist=modlist)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
252
plugins/modules/net_tools/ldap/ldap_entry.py
Normal file
252
plugins/modules/net_tools/ldap/ldap_entry.py
Normal file
@@ -0,0 +1,252 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2016, Peter Sagerson <psagers@ignorare.net>
|
||||
# Copyright: (c) 2016, Jiri Tyr <jiri.tyr@gmail.com>
|
||||
#
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {
|
||||
'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'
|
||||
}
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: ldap_entry
|
||||
short_description: Add or remove LDAP entries.
|
||||
description:
|
||||
- Add or remove LDAP entries. This module only asserts the existence or
|
||||
non-existence of an LDAP entry, not its attributes. To assert the
|
||||
attribute values of an entry, see M(ldap_attr).
|
||||
notes:
|
||||
- The default authentication settings will attempt to use a SASL EXTERNAL
|
||||
bind over a UNIX domain socket. This works well with the default Ubuntu
|
||||
install for example, which includes a cn=peercred,cn=external,cn=auth ACL
|
||||
rule allowing root to modify the server configuration. If you need to use
|
||||
a simple bind to access your server, pass the credentials in I(bind_dn)
|
||||
and I(bind_pw).
|
||||
author:
|
||||
- Jiri Tyr (@jtyr)
|
||||
requirements:
|
||||
- python-ldap
|
||||
options:
|
||||
attributes:
|
||||
description:
|
||||
- If I(state=present), attributes necessary to create an entry. Existing
|
||||
entries are never modified. To assert specific attribute values on an
|
||||
existing entry, use M(ldap_attr) module instead.
|
||||
objectClass:
|
||||
description:
|
||||
- If I(state=present), value or list of values to use when creating
|
||||
the entry. It can either be a string or an actual list of
|
||||
strings.
|
||||
params:
|
||||
description:
|
||||
- List of options which allows to overwrite any of the task or the
|
||||
I(attributes) options. To remove an option, set the value of the option
|
||||
to C(null).
|
||||
state:
|
||||
description:
|
||||
- The target state of the entry.
|
||||
choices: [present, absent]
|
||||
default: present
|
||||
extends_documentation_fragment:
|
||||
- community.general.ldap.documentation
|
||||
|
||||
'''
|
||||
|
||||
|
||||
EXAMPLES = """
|
||||
- name: Make sure we have a parent entry for users
|
||||
ldap_entry:
|
||||
dn: ou=users,dc=example,dc=com
|
||||
objectClass: organizationalUnit
|
||||
|
||||
- name: Make sure we have an admin user
|
||||
ldap_entry:
|
||||
dn: cn=admin,dc=example,dc=com
|
||||
objectClass:
|
||||
- simpleSecurityObject
|
||||
- organizationalRole
|
||||
attributes:
|
||||
description: An LDAP administrator
|
||||
userPassword: "{SSHA}tabyipcHzhwESzRaGA7oQ/SDoBZQOGND"
|
||||
|
||||
- name: Get rid of an old entry
|
||||
ldap_entry:
|
||||
dn: ou=stuff,dc=example,dc=com
|
||||
state: absent
|
||||
server_uri: ldap://localhost/
|
||||
bind_dn: cn=admin,dc=example,dc=com
|
||||
bind_pw: password
|
||||
|
||||
#
|
||||
# The same as in the previous example but with the authentication details
|
||||
# stored in the ldap_auth variable:
|
||||
#
|
||||
# ldap_auth:
|
||||
# server_uri: ldap://localhost/
|
||||
# bind_dn: cn=admin,dc=example,dc=com
|
||||
# bind_pw: password
|
||||
- name: Get rid of an old entry
|
||||
ldap_entry:
|
||||
dn: ou=stuff,dc=example,dc=com
|
||||
state: absent
|
||||
params: "{{ ldap_auth }}"
|
||||
"""
|
||||
|
||||
|
||||
RETURN = """
|
||||
# Default return values
|
||||
"""
|
||||
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
from ansible.module_utils.six import string_types
|
||||
from ansible.module_utils._text import to_native, to_bytes
|
||||
from ansible_collections.community.general.plugins.module_utils.ldap import LdapGeneric, gen_specs
|
||||
|
||||
LDAP_IMP_ERR = None
|
||||
try:
|
||||
import ldap.modlist
|
||||
|
||||
HAS_LDAP = True
|
||||
except ImportError:
|
||||
LDAP_IMP_ERR = traceback.format_exc()
|
||||
HAS_LDAP = False
|
||||
|
||||
|
||||
class LdapEntry(LdapGeneric):
|
||||
def __init__(self, module):
|
||||
LdapGeneric.__init__(self, module)
|
||||
|
||||
# Shortcuts
|
||||
self.state = self.module.params['state']
|
||||
|
||||
# Add the objectClass into the list of attributes
|
||||
self.module.params['attributes']['objectClass'] = (
|
||||
self.module.params['objectClass'])
|
||||
|
||||
# Load attributes
|
||||
if self.state == 'present':
|
||||
self.attrs = self._load_attrs()
|
||||
|
||||
def _load_attrs(self):
|
||||
""" Turn attribute's value to array. """
|
||||
attrs = {}
|
||||
|
||||
for name, value in self.module.params['attributes'].items():
|
||||
if name not in attrs:
|
||||
attrs[name] = []
|
||||
|
||||
if isinstance(value, list):
|
||||
attrs[name] = list(map(to_bytes, value))
|
||||
else:
|
||||
attrs[name].append(to_bytes(value))
|
||||
|
||||
return attrs
|
||||
|
||||
def add(self):
|
||||
""" If self.dn does not exist, returns a callable that will add it. """
|
||||
def _add():
|
||||
self.connection.add_s(self.dn, modlist)
|
||||
|
||||
if not self._is_entry_present():
|
||||
modlist = ldap.modlist.addModlist(self.attrs)
|
||||
action = _add
|
||||
else:
|
||||
action = None
|
||||
|
||||
return action
|
||||
|
||||
def delete(self):
|
||||
""" If self.dn exists, returns a callable that will delete it. """
|
||||
def _delete():
|
||||
self.connection.delete_s(self.dn)
|
||||
|
||||
if self._is_entry_present():
|
||||
action = _delete
|
||||
else:
|
||||
action = None
|
||||
|
||||
return action
|
||||
|
||||
def _is_entry_present(self):
|
||||
try:
|
||||
self.connection.search_s(self.dn, ldap.SCOPE_BASE)
|
||||
except ldap.NO_SUCH_OBJECT:
|
||||
is_present = False
|
||||
else:
|
||||
is_present = True
|
||||
|
||||
return is_present
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=gen_specs(
|
||||
attributes=dict(default={}, type='dict'),
|
||||
objectClass=dict(type='raw'),
|
||||
params=dict(type='dict'),
|
||||
state=dict(default='present', choices=['present', 'absent']),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
if not HAS_LDAP:
|
||||
module.fail_json(msg=missing_required_lib('python-ldap'),
|
||||
exception=LDAP_IMP_ERR)
|
||||
|
||||
state = module.params['state']
|
||||
|
||||
# Check if objectClass is present when needed
|
||||
if state == 'present' and module.params['objectClass'] is None:
|
||||
module.fail_json(msg="At least one objectClass must be provided.")
|
||||
|
||||
# Check if objectClass is of the correct type
|
||||
if (
|
||||
module.params['objectClass'] is not None and not (
|
||||
isinstance(module.params['objectClass'], string_types) or
|
||||
isinstance(module.params['objectClass'], list))):
|
||||
module.fail_json(msg="objectClass must be either a string or a list.")
|
||||
|
||||
# Update module parameters with user's parameters if defined
|
||||
if 'params' in module.params and isinstance(module.params['params'], dict):
|
||||
for key, val in module.params['params'].items():
|
||||
if key in module.argument_spec:
|
||||
module.params[key] = val
|
||||
else:
|
||||
module.params['attributes'][key] = val
|
||||
|
||||
# Remove the params
|
||||
module.params.pop('params', None)
|
||||
|
||||
# Instantiate the LdapEntry object
|
||||
ldap = LdapEntry(module)
|
||||
|
||||
# Get the action function
|
||||
if state == 'present':
|
||||
action = ldap.add()
|
||||
elif state == 'absent':
|
||||
action = ldap.delete()
|
||||
|
||||
# Perform the action
|
||||
if action is not None and not module.check_mode:
|
||||
try:
|
||||
action()
|
||||
except Exception as e:
|
||||
module.fail_json(msg="Entry action failed.", details=to_native(e), exception=traceback.format_exc())
|
||||
|
||||
module.exit_json(changed=(action is not None))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
150
plugins/modules/net_tools/ldap/ldap_passwd.py
Normal file
150
plugins/modules/net_tools/ldap/ldap_passwd.py
Normal file
@@ -0,0 +1,150 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2017-2018, Keller Fuchs <kellerfuchs@hashbang.sh>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {
|
||||
'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'
|
||||
}
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: ldap_passwd
|
||||
short_description: Set passwords in LDAP.
|
||||
description:
|
||||
- Set a password for an LDAP entry. This module only asserts that
|
||||
a given password is valid for a given entry. To assert the
|
||||
existence of an entry, see M(ldap_entry).
|
||||
notes:
|
||||
- The default authentication settings will attempt to use a SASL EXTERNAL
|
||||
bind over a UNIX domain socket. This works well with the default Ubuntu
|
||||
install for example, which includes a cn=peercred,cn=external,cn=auth ACL
|
||||
rule allowing root to modify the server configuration. If you need to use
|
||||
a simple bind to access your server, pass the credentials in I(bind_dn)
|
||||
and I(bind_pw).
|
||||
author:
|
||||
- Keller Fuchs (@KellerFuchs)
|
||||
requirements:
|
||||
- python-ldap
|
||||
options:
|
||||
passwd:
|
||||
required: true
|
||||
description:
|
||||
- The (plaintext) password to be set for I(dn).
|
||||
extends_documentation_fragment:
|
||||
- community.general.ldap.documentation
|
||||
|
||||
'''
|
||||
|
||||
EXAMPLES = """
|
||||
- name: Set a password for the admin user
|
||||
ldap_passwd:
|
||||
dn: cn=admin,dc=example,dc=com
|
||||
passwd: "{{ vault_secret }}"
|
||||
|
||||
- name: Setting passwords in bulk
|
||||
ldap_passwd:
|
||||
dn: "{{ item.key }}"
|
||||
passwd: "{{ item.value }}"
|
||||
with_dict:
|
||||
alice: alice123123
|
||||
bob: "|30b!"
|
||||
admin: "{{ vault_secret }}"
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
modlist:
|
||||
description: list of modified parameters
|
||||
returned: success
|
||||
type: list
|
||||
sample: '[[2, "olcRootDN", ["cn=root,dc=example,dc=com"]]]'
|
||||
"""
|
||||
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
from ansible_collections.community.general.plugins.module_utils.ldap import LdapGeneric, gen_specs
|
||||
|
||||
LDAP_IMP_ERR = None
|
||||
try:
|
||||
import ldap
|
||||
|
||||
HAS_LDAP = True
|
||||
except ImportError:
|
||||
LDAP_IMP_ERR = traceback.format_exc()
|
||||
HAS_LDAP = False
|
||||
|
||||
|
||||
class LdapPasswd(LdapGeneric):
|
||||
def __init__(self, module):
|
||||
LdapGeneric.__init__(self, module)
|
||||
|
||||
# Shortcuts
|
||||
self.passwd = self.module.params['passwd']
|
||||
|
||||
def passwd_check(self):
|
||||
try:
|
||||
tmp_con = ldap.initialize(self.server_uri)
|
||||
except ldap.LDAPError as e:
|
||||
self.fail("Cannot initialize LDAP connection", e)
|
||||
|
||||
if self.start_tls:
|
||||
try:
|
||||
tmp_con.start_tls_s()
|
||||
except ldap.LDAPError as e:
|
||||
self.fail("Cannot start TLS.", e)
|
||||
|
||||
try:
|
||||
tmp_con.simple_bind_s(self.dn, self.passwd)
|
||||
except ldap.INVALID_CREDENTIALS:
|
||||
return True
|
||||
except ldap.LDAPError as e:
|
||||
self.fail("Cannot bind to the server.", e)
|
||||
else:
|
||||
return False
|
||||
finally:
|
||||
tmp_con.unbind()
|
||||
|
||||
def passwd_set(self):
|
||||
# Exit early if the password is already valid
|
||||
if not self.passwd_check():
|
||||
return False
|
||||
|
||||
# Change the password (or throw an exception)
|
||||
try:
|
||||
self.connection.passwd_s(self.dn, None, self.passwd)
|
||||
except ldap.LDAPError as e:
|
||||
self.fail("Unable to set password", e)
|
||||
|
||||
# Password successfully changed
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=gen_specs(passwd=dict(no_log=True)),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
if not HAS_LDAP:
|
||||
module.fail_json(msg=missing_required_lib('python-ldap'),
|
||||
exception=LDAP_IMP_ERR)
|
||||
|
||||
ldap = LdapPasswd(module)
|
||||
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=ldap.passwd_check())
|
||||
|
||||
module.exit_json(changed=ldap.passwd_set())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
84
plugins/modules/net_tools/lldp.py
Normal file
84
plugins/modules/net_tools/lldp.py
Normal file
@@ -0,0 +1,84 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# Copyright: Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: lldp
|
||||
requirements: [ lldpctl ]
|
||||
short_description: get details reported by lldp
|
||||
description:
|
||||
- Reads data out of lldpctl
|
||||
options: {}
|
||||
author: "Andy Hill (@andyhky)"
|
||||
notes:
|
||||
- Requires lldpd running and lldp enabled on switches
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Retrieve switch/port information
|
||||
- name: Gather information from lldp
|
||||
lldp:
|
||||
|
||||
- name: Print each switch/port
|
||||
debug:
|
||||
msg: "{{ lldp[item]['chassis']['name'] }} / {{ lldp[item]['port']['ifname'] }}"
|
||||
with_items: "{{ lldp.keys() }}"
|
||||
|
||||
# TASK: [Print each switch/port] ***********************************************************
|
||||
# ok: [10.13.0.22] => (item=eth2) => {"item": "eth2", "msg": "switch1.example.com / Gi0/24"}
|
||||
# ok: [10.13.0.22] => (item=eth1) => {"item": "eth1", "msg": "switch2.example.com / Gi0/3"}
|
||||
# ok: [10.13.0.22] => (item=eth0) => {"item": "eth0", "msg": "switch3.example.com / Gi0/3"}
|
||||
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
|
||||
def gather_lldp(module):
|
||||
cmd = ['lldpctl', '-f', 'keyvalue']
|
||||
rc, output, err = module.run_command(cmd)
|
||||
if output:
|
||||
output_dict = {}
|
||||
current_dict = {}
|
||||
lldp_entries = output.split("\n")
|
||||
|
||||
for entry in lldp_entries:
|
||||
if entry.startswith('lldp'):
|
||||
path, value = entry.strip().split("=", 1)
|
||||
path = path.split(".")
|
||||
path_components, final = path[:-1], path[-1]
|
||||
else:
|
||||
value = current_dict[final] + '\n' + entry
|
||||
|
||||
current_dict = output_dict
|
||||
for path_component in path_components:
|
||||
current_dict[path_component] = current_dict.get(path_component, {})
|
||||
current_dict = current_dict[path_component]
|
||||
current_dict[final] = value
|
||||
return output_dict
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule({})
|
||||
|
||||
lldp_output = gather_lldp(module)
|
||||
try:
|
||||
data = {'lldp': lldp_output['lldp']}
|
||||
module.exit_json(ansible_facts=data)
|
||||
except TypeError:
|
||||
module.fail_json(msg="lldpctl command failed. is lldpd running?")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
265
plugins/modules/net_tools/netcup_dns.py
Normal file
265
plugins/modules/net_tools/netcup_dns.py
Normal file
@@ -0,0 +1,265 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2018 Nicolai Buchwitz <nb@tipi-net.de>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {
|
||||
'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'
|
||||
}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: netcup_dns
|
||||
notes: []
|
||||
short_description: manage Netcup DNS records
|
||||
description:
|
||||
- "Manages DNS records via the Netcup API, see the docs U(https://ccp.netcup.net/run/webservice/servers/endpoint.php)"
|
||||
options:
|
||||
api_key:
|
||||
description:
|
||||
- API key for authentication, must be obtained via the netcup CCP (U(https://ccp.netcup.net))
|
||||
required: True
|
||||
api_password:
|
||||
description:
|
||||
- API password for authentication, must be obtained via the netcup CCP (https://ccp.netcup.net)
|
||||
required: True
|
||||
customer_id:
|
||||
description:
|
||||
- Netcup customer id
|
||||
required: True
|
||||
domain:
|
||||
description:
|
||||
- Domainname the records should be added / removed
|
||||
required: True
|
||||
record:
|
||||
description:
|
||||
- Record to add or delete, supports wildcard (*). Default is C(@) (e.g. the zone name)
|
||||
default: "@"
|
||||
aliases: [ name ]
|
||||
type:
|
||||
description:
|
||||
- Record type
|
||||
choices: ['A', 'AAAA', 'MX', 'CNAME', 'CAA', 'SRV', 'TXT', 'TLSA', 'NS', 'DS']
|
||||
required: True
|
||||
value:
|
||||
description:
|
||||
- Record value
|
||||
required: true
|
||||
solo:
|
||||
type: bool
|
||||
default: False
|
||||
description:
|
||||
- Whether the record should be the only one for that record type and record name. Only use with C(state=present)
|
||||
- This will delete all other records with the same record name and type.
|
||||
priority:
|
||||
description:
|
||||
- Record priority. Required for C(type=MX)
|
||||
required: False
|
||||
state:
|
||||
description:
|
||||
- Whether the record should exist or not
|
||||
required: False
|
||||
default: present
|
||||
choices: [ 'present', 'absent' ]
|
||||
requirements:
|
||||
- "nc-dnsapi >= 0.1.3"
|
||||
author: "Nicolai Buchwitz (@nbuchwitz)"
|
||||
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Create a record of type A
|
||||
netcup_dns:
|
||||
api_key: "..."
|
||||
api_password: "..."
|
||||
customer_id: "..."
|
||||
domain: "example.com"
|
||||
name: "mail"
|
||||
type: "A"
|
||||
value: "127.0.0.1"
|
||||
|
||||
- name: Delete that record
|
||||
netcup_dns:
|
||||
api_key: "..."
|
||||
api_password: "..."
|
||||
customer_id: "..."
|
||||
domain: "example.com"
|
||||
name: "mail"
|
||||
type: "A"
|
||||
value: "127.0.0.1"
|
||||
state: absent
|
||||
|
||||
- name: Create a wildcard record
|
||||
netcup_dns:
|
||||
api_key: "..."
|
||||
api_password: "..."
|
||||
customer_id: "..."
|
||||
domain: "example.com"
|
||||
name: "*"
|
||||
type: "A"
|
||||
value: "127.0.1.1"
|
||||
|
||||
- name: Set the MX record for example.com
|
||||
netcup_dns:
|
||||
api_key: "..."
|
||||
api_password: "..."
|
||||
customer_id: "..."
|
||||
domain: "example.com"
|
||||
type: "MX"
|
||||
value: "mail.example.com"
|
||||
|
||||
- name: Set a record and ensure that this is the only one
|
||||
netcup_dns:
|
||||
api_key: "..."
|
||||
api_password: "..."
|
||||
customer_id: "..."
|
||||
name: "demo"
|
||||
domain: "example.com"
|
||||
type: "AAAA"
|
||||
value: "::1"
|
||||
solo: true
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
records:
|
||||
description: list containing all records
|
||||
returned: success
|
||||
type: complex
|
||||
contains:
|
||||
name:
|
||||
description: the record name
|
||||
returned: success
|
||||
type: str
|
||||
sample: fancy-hostname
|
||||
type:
|
||||
description: the record type
|
||||
returned: succcess
|
||||
type: str
|
||||
sample: A
|
||||
value:
|
||||
description: the record destination
|
||||
returned: success
|
||||
type: str
|
||||
sample: 127.0.0.1
|
||||
priority:
|
||||
description: the record priority (only relevant if type=MX)
|
||||
returned: success
|
||||
type: int
|
||||
sample: 0
|
||||
id:
|
||||
description: internal id of the record
|
||||
returned: success
|
||||
type: int
|
||||
sample: 12345
|
||||
'''
|
||||
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
|
||||
NCDNSAPI_IMP_ERR = None
|
||||
try:
|
||||
import nc_dnsapi
|
||||
from nc_dnsapi import DNSRecord
|
||||
|
||||
HAS_NCDNSAPI = True
|
||||
except ImportError:
|
||||
NCDNSAPI_IMP_ERR = traceback.format_exc()
|
||||
HAS_NCDNSAPI = False
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
api_key=dict(required=True, no_log=True),
|
||||
api_password=dict(required=True, no_log=True),
|
||||
customer_id=dict(required=True, type='int'),
|
||||
|
||||
domain=dict(required=True),
|
||||
record=dict(required=False, default='@', aliases=['name']),
|
||||
type=dict(required=True, choices=['A', 'AAAA', 'MX', 'CNAME', 'CAA', 'SRV', 'TXT', 'TLSA', 'NS', 'DS']),
|
||||
value=dict(required=True),
|
||||
priority=dict(required=False, type='int'),
|
||||
solo=dict(required=False, type='bool', default=False),
|
||||
state=dict(required=False, choices=['present', 'absent'], default='present'),
|
||||
|
||||
),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
if not HAS_NCDNSAPI:
|
||||
module.fail_json(msg=missing_required_lib('nc-dnsapi'), exception=NCDNSAPI_IMP_ERR)
|
||||
|
||||
api_key = module.params.get('api_key')
|
||||
api_password = module.params.get('api_password')
|
||||
customer_id = module.params.get('customer_id')
|
||||
domain = module.params.get('domain')
|
||||
record_type = module.params.get('type')
|
||||
record = module.params.get('record')
|
||||
value = module.params.get('value')
|
||||
priority = module.params.get('priority')
|
||||
solo = module.params.get('solo')
|
||||
state = module.params.get('state')
|
||||
|
||||
if record_type == 'MX' and not priority:
|
||||
module.fail_json(msg="record type MX required the 'priority' argument")
|
||||
|
||||
has_changed = False
|
||||
all_records = []
|
||||
try:
|
||||
with nc_dnsapi.Client(customer_id, api_key, api_password) as api:
|
||||
all_records = api.dns_records(domain)
|
||||
record = DNSRecord(record, record_type, value, priority=priority)
|
||||
|
||||
# try to get existing record
|
||||
record_exists = False
|
||||
for r in all_records:
|
||||
if r == record:
|
||||
record_exists = True
|
||||
record = r
|
||||
|
||||
break
|
||||
|
||||
if state == 'present':
|
||||
if solo:
|
||||
obsolete_records = [r for r in all_records if
|
||||
r.hostname == record.hostname
|
||||
and r.type == record.type
|
||||
and not r.destination == record.destination]
|
||||
|
||||
if obsolete_records:
|
||||
if not module.check_mode:
|
||||
all_records = api.delete_dns_records(domain, obsolete_records)
|
||||
|
||||
has_changed = True
|
||||
|
||||
if not record_exists:
|
||||
if not module.check_mode:
|
||||
all_records = api.add_dns_record(domain, record)
|
||||
|
||||
has_changed = True
|
||||
elif state == 'absent' and record_exists:
|
||||
if not module.check_mode:
|
||||
all_records = api.delete_dns_record(domain, record)
|
||||
|
||||
has_changed = True
|
||||
|
||||
except Exception as ex:
|
||||
module.fail_json(msg=ex.message)
|
||||
|
||||
module.exit_json(changed=has_changed, result={"records": [record_data(r) for r in all_records]})
|
||||
|
||||
|
||||
def record_data(r):
|
||||
return {"name": r.hostname, "type": r.type, "value": r.destination, "priority": r.priority, "id": r.id}
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
174
plugins/modules/net_tools/nios/nios_a_record.py
Normal file
174
plugins/modules/net_tools/nios/nios_a_record.py
Normal file
@@ -0,0 +1,174 @@
|
||||
#!/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_a_record
|
||||
author: "Blair Rampling (@brampling)"
|
||||
short_description: Configure Infoblox NIOS A records
|
||||
description:
|
||||
- Adds and/or removes instances of A record objects from
|
||||
Infoblox NIOS servers. This module manages NIOS C(record:a) objects
|
||||
using the Infoblox WAPI interface over REST.
|
||||
requirements:
|
||||
- infoblox-client
|
||||
extends_documentation_fragment:
|
||||
- community.general.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 A record with. The DNS
|
||||
view must already be configured on the system
|
||||
required: true
|
||||
default: default
|
||||
aliases:
|
||||
- dns_view
|
||||
ipv4addr:
|
||||
description:
|
||||
- Configures the IPv4 address for this A record. Users can dynamically
|
||||
allocate ipv4 address to A record by passing dictionary containing,
|
||||
I(nios_next_ip) and I(CIDR network range). See example
|
||||
required: true
|
||||
aliases:
|
||||
- ipv4
|
||||
ttl:
|
||||
description:
|
||||
- Configures the TTL to be associated with this A 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: configure an A record
|
||||
nios_a_record:
|
||||
name: a.ansible.com
|
||||
ipv4: 192.168.10.1
|
||||
state: present
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
|
||||
- name: add a comment to an existing A record
|
||||
nios_a_record:
|
||||
name: a.ansible.com
|
||||
ipv4: 192.168.10.1
|
||||
comment: this is a test comment
|
||||
state: present
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
|
||||
- name: remove an A record from the system
|
||||
nios_a_record:
|
||||
name: a.ansible.com
|
||||
ipv4: 192.168.10.1
|
||||
state: absent
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
|
||||
- name: update an A record name
|
||||
nios_a_record:
|
||||
name: {new_name: a_new.ansible.com, old_name: a.ansible.com}
|
||||
ipv4: 192.168.10.1
|
||||
state: present
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
|
||||
- name: dynamically add a record to next available ip
|
||||
nios_a_record:
|
||||
name: a.ansible.com
|
||||
ipv4: {nios_next_ip: 192.168.10.0/24}
|
||||
state: present
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
'''
|
||||
|
||||
RETURN = ''' # '''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.six import iteritems
|
||||
from ansible_collections.community.general.plugins.module_utils.net_tools.nios.api import WapiModule
|
||||
from ansible_collections.community.general.plugins.module_utils.net_tools.nios.api import NIOS_A_RECORD
|
||||
|
||||
|
||||
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),
|
||||
|
||||
ipv4addr=dict(aliases=['ipv4'], 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(NIOS_A_RECORD, ib_spec)
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
161
plugins/modules/net_tools/nios/nios_aaaa_record.py
Normal file
161
plugins/modules/net_tools/nios/nios_aaaa_record.py
Normal file
@@ -0,0 +1,161 @@
|
||||
#!/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_aaaa_record
|
||||
author: "Blair Rampling (@brampling)"
|
||||
short_description: Configure Infoblox NIOS AAAA records
|
||||
description:
|
||||
- Adds and/or removes instances of AAAA record objects from
|
||||
Infoblox NIOS servers. This module manages NIOS C(record:aaaa) objects
|
||||
using the Infoblox WAPI interface over REST.
|
||||
requirements:
|
||||
- infoblox-client
|
||||
extends_documentation_fragment:
|
||||
- community.general.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 AAAA record with. The DNS
|
||||
view must already be configured on the system
|
||||
required: true
|
||||
default: default
|
||||
aliases:
|
||||
- dns_view
|
||||
ipv6addr:
|
||||
description:
|
||||
- Configures the IPv6 address for this AAAA record.
|
||||
required: true
|
||||
aliases:
|
||||
- ipv6
|
||||
ttl:
|
||||
description:
|
||||
- Configures the TTL to be associated with this AAAA 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: configure an AAAA record
|
||||
nios_aaaa_record:
|
||||
name: aaaa.ansible.com
|
||||
ipv6: 2001:0db8:85a3:0000:0000:8a2e:0370:7334
|
||||
state: present
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
|
||||
- name: add a comment to an existing AAAA record
|
||||
nios_aaaa_record:
|
||||
name: aaaa.ansible.com
|
||||
ipv6: 2001:0db8:85a3:0000:0000:8a2e:0370:7334
|
||||
comment: this is a test comment
|
||||
state: present
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
|
||||
- name: remove an AAAA record from the system
|
||||
nios_aaaa_record:
|
||||
name: aaaa.ansible.com
|
||||
ipv6: 2001:0db8:85a3:0000:0000:8a2e:0370:7334
|
||||
state: absent
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
|
||||
- name: update an AAAA record name
|
||||
nios_aaaa_record:
|
||||
name: {new_name: aaaa_new.ansible.com, old_name: aaaa.ansible.com}
|
||||
ipv6: 2001:0db8:85a3:0000:0000:8a2e:0370:7334
|
||||
state: present
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
'''
|
||||
|
||||
RETURN = ''' # '''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.six import iteritems
|
||||
from ansible_collections.community.general.plugins.module_utils.net_tools.nios.api import WapiModule
|
||||
from ansible_collections.community.general.plugins.module_utils.net_tools.nios.api import NIOS_AAAA_RECORD
|
||||
|
||||
|
||||
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),
|
||||
|
||||
ipv6addr=dict(aliases=['ipv6'], 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(NIOS_AAAA_RECORD, ib_spec)
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
150
plugins/modules/net_tools/nios/nios_cname_record.py
Normal file
150
plugins/modules/net_tools/nios/nios_cname_record.py
Normal file
@@ -0,0 +1,150 @@
|
||||
#!/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_cname_record
|
||||
author: "Blair Rampling (@brampling)"
|
||||
short_description: Configure Infoblox NIOS CNAME records
|
||||
description:
|
||||
- Adds and/or removes instances of CNAME record objects from
|
||||
Infoblox NIOS servers. This module manages NIOS C(record:cname) objects
|
||||
using the Infoblox WAPI interface over REST.
|
||||
requirements:
|
||||
- infoblox-client
|
||||
extends_documentation_fragment:
|
||||
- community.general.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 CNAME record with. The DNS
|
||||
view must already be configured on the system
|
||||
required: true
|
||||
default: default
|
||||
aliases:
|
||||
- dns_view
|
||||
canonical:
|
||||
description:
|
||||
- Configures the canonical name for this CNAME record.
|
||||
required: true
|
||||
aliases:
|
||||
- cname
|
||||
ttl:
|
||||
description:
|
||||
- Configures the TTL to be associated with this CNAME 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: configure a CNAME record
|
||||
nios_cname_record:
|
||||
name: cname.ansible.com
|
||||
canonical: realhost.ansible.com
|
||||
state: present
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
|
||||
- name: add a comment to an existing CNAME record
|
||||
nios_cname_record:
|
||||
name: cname.ansible.com
|
||||
canonical: realhost.ansible.com
|
||||
comment: this is a test comment
|
||||
state: present
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
|
||||
- name: remove a CNAME record from the system
|
||||
nios_cname_record:
|
||||
name: cname.ansible.com
|
||||
canonical: realhost.ansible.com
|
||||
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.six import iteritems
|
||||
from ansible_collections.community.general.plugins.module_utils.net_tools.nios.api import WapiModule
|
||||
from ansible_collections.community.general.plugins.module_utils.net_tools.nios.api import NIOS_CNAME_RECORD
|
||||
|
||||
|
||||
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),
|
||||
|
||||
canonical=dict(aliases=['cname'], 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(NIOS_CNAME_RECORD, ib_spec)
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
145
plugins/modules/net_tools/nios/nios_dns_view.py
Normal file
145
plugins/modules/net_tools/nios/nios_dns_view.py
Normal file
@@ -0,0 +1,145 @@
|
||||
#!/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_dns_view
|
||||
author: "Peter Sprygada (@privateip)"
|
||||
short_description: Configure Infoblox NIOS DNS views
|
||||
description:
|
||||
- Adds and/or removes instances of DNS view objects from
|
||||
Infoblox NIOS servers. This module manages NIOS C(view) objects
|
||||
using the Infoblox WAPI interface over REST.
|
||||
- Updates instances of DNS view object from Infoblox NIOS servers.
|
||||
requirements:
|
||||
- infoblox-client
|
||||
extends_documentation_fragment:
|
||||
- community.general.nios
|
||||
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Specifies the fully qualified hostname to add or remove from
|
||||
the system. User can also update the hostname as it is possible
|
||||
to pass a dict containing I(new_name), I(old_name). See examples.
|
||||
required: true
|
||||
aliases:
|
||||
- view
|
||||
network_view:
|
||||
description:
|
||||
- Specifies the name of the network view to assign the configured
|
||||
DNS view to. The network view must already be configured on the
|
||||
target system.
|
||||
required: true
|
||||
default: default
|
||||
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.
|
||||
required: false
|
||||
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.
|
||||
required: false
|
||||
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.
|
||||
required: false
|
||||
default: present
|
||||
choices:
|
||||
- present
|
||||
- absent
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: configure a new dns view instance
|
||||
nios_dns_view:
|
||||
name: ansible-dns
|
||||
state: present
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
- name: update the comment for dns view
|
||||
nios_dns_view:
|
||||
name: ansible-dns
|
||||
comment: this is an example comment
|
||||
state: present
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
- name: remove the dns view instance
|
||||
nios_dns_view:
|
||||
name: ansible-dns
|
||||
state: absent
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
- name: update the dns view instance
|
||||
nios_dns_view:
|
||||
name: {new_name: ansible-dns-new, old_name: ansible-dns}
|
||||
state: present
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
'''
|
||||
|
||||
RETURN = ''' # '''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible_collections.community.general.plugins.module_utils.net_tools.nios.api import WapiModule
|
||||
from ansible_collections.community.general.plugins.module_utils.net_tools.nios.api import NIOS_DNS_VIEW
|
||||
|
||||
|
||||
def main():
|
||||
''' Main entry point for module execution
|
||||
'''
|
||||
ib_spec = dict(
|
||||
name=dict(required=True, aliases=['view'], ib_req=True),
|
||||
network_view=dict(default='default', ib_req=True),
|
||||
|
||||
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(NIOS_DNS_VIEW, ib_spec)
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
270
plugins/modules/net_tools/nios/nios_fixed_address.py
Normal file
270
plugins/modules/net_tools/nios/nios_fixed_address.py
Normal file
@@ -0,0 +1,270 @@
|
||||
#!/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_fixed_address
|
||||
author: "Sumit Jaiswal (@sjaiswal)"
|
||||
short_description: Configure Infoblox NIOS DHCP Fixed Address
|
||||
description:
|
||||
- A fixed address is a specific IP address that a DHCP server
|
||||
always assigns when a lease request comes from a particular
|
||||
MAC address of the client.
|
||||
- Supports both IPV4 and IPV6 internet protocols
|
||||
requirements:
|
||||
- infoblox-client
|
||||
extends_documentation_fragment:
|
||||
- community.general.nios
|
||||
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Specifies the hostname with which fixed DHCP ip-address is stored
|
||||
for respective mac.
|
||||
required: false
|
||||
ipaddr:
|
||||
description:
|
||||
- IPV4/V6 address of the fixed address.
|
||||
required: true
|
||||
mac:
|
||||
description:
|
||||
- The MAC address of the interface.
|
||||
required: true
|
||||
network:
|
||||
description:
|
||||
- Specifies the network range in which ipaddr exists.
|
||||
aliases:
|
||||
- network
|
||||
network_view:
|
||||
description:
|
||||
- Configures the name of the network view to associate with this
|
||||
configured instance.
|
||||
required: false
|
||||
default: default
|
||||
options:
|
||||
description:
|
||||
- Configures the set of DHCP options to be included as part of
|
||||
the configured network instance. This argument accepts a list
|
||||
of values (see suboptions). When configuring suboptions at
|
||||
least one of C(name) or C(num) must be specified.
|
||||
suboptions:
|
||||
name:
|
||||
description:
|
||||
- The name of the DHCP option to configure
|
||||
num:
|
||||
description:
|
||||
- The number of the DHCP option to configure
|
||||
value:
|
||||
description:
|
||||
- The value of the DHCP option specified by C(name)
|
||||
required: true
|
||||
use_option:
|
||||
description:
|
||||
- Only applies to a subset of options (see NIOS API documentation)
|
||||
type: bool
|
||||
default: 'yes'
|
||||
vendor_class:
|
||||
description:
|
||||
- The name of the space this DHCP option is associated to
|
||||
default: DHCP
|
||||
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 ipv4 dhcp fixed address
|
||||
nios_fixed_address:
|
||||
name: ipv4_fixed
|
||||
ipaddr: 192.168.10.1
|
||||
mac: 08:6d:41:e8:fd:e8
|
||||
network: 192.168.10.0/24
|
||||
network_view: default
|
||||
comment: this is a test comment
|
||||
state: present
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
- name: configure a ipv6 dhcp fixed address
|
||||
nios_fixed_address:
|
||||
name: ipv6_fixed
|
||||
ipaddr: fe80::1/10
|
||||
mac: 08:6d:41:e8:fd:e8
|
||||
network: fe80::/64
|
||||
network_view: default
|
||||
comment: this is a test comment
|
||||
state: present
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
- name: set dhcp options for a ipv4 fixed address
|
||||
nios_fixed_address:
|
||||
name: ipv4_fixed
|
||||
ipaddr: 192.168.10.1
|
||||
mac: 08:6d:41:e8:fd:e8
|
||||
network: 192.168.10.0/24
|
||||
network_view: default
|
||||
comment: this is a test comment
|
||||
options:
|
||||
- name: domain-name
|
||||
value: ansible.com
|
||||
state: present
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
- name: remove a ipv4 dhcp fixed address
|
||||
nios_fixed_address:
|
||||
name: ipv4_fixed
|
||||
ipaddr: 192.168.10.1
|
||||
mac: 08:6d:41:e8:fd:e8
|
||||
network: 192.168.10.0/24
|
||||
network_view: default
|
||||
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.six import iteritems
|
||||
from ansible_collections.community.general.plugins.module_utils.net_tools.nios.api import WapiModule
|
||||
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import validate_ip_address, validate_ip_v6_address
|
||||
from ansible_collections.community.general.plugins.module_utils.net_tools.nios.api import NIOS_IPV4_FIXED_ADDRESS, NIOS_IPV6_FIXED_ADDRESS
|
||||
|
||||
|
||||
def options(module):
|
||||
''' Transforms the module argument into a valid WAPI struct
|
||||
This function will transform the options argument into a structure that
|
||||
is a valid WAPI structure in the format of:
|
||||
{
|
||||
name: <value>,
|
||||
num: <value>,
|
||||
value: <value>,
|
||||
use_option: <value>,
|
||||
vendor_class: <value>
|
||||
}
|
||||
It will remove any options that are set to None since WAPI will error on
|
||||
that condition. The use_option field only applies
|
||||
to special options that are displayed separately from other options and
|
||||
have a use flag. This function removes the use_option flag from all
|
||||
other options. It will also verify that either `name` or `num` is
|
||||
set in the structure but does not validate the values are equal.
|
||||
The remainder of the value validation is performed by WAPI
|
||||
'''
|
||||
special_options = ['routers', 'router-templates', 'domain-name-servers',
|
||||
'domain-name', 'broadcast-address', 'broadcast-address-offset',
|
||||
'dhcp-lease-time', 'dhcp6.name-servers']
|
||||
options = list()
|
||||
for item in module.params['options']:
|
||||
opt = dict([(k, v) for k, v in iteritems(item) if v is not None])
|
||||
if 'name' not in opt and 'num' not in opt:
|
||||
module.fail_json(msg='one of `name` or `num` is required for option value')
|
||||
if opt['name'] not in special_options:
|
||||
del opt['use_option']
|
||||
options.append(opt)
|
||||
return options
|
||||
|
||||
|
||||
def validate_ip_addr_type(ip, arg_spec, module):
|
||||
'''This function will check if the argument ip is type v4/v6 and return appropriate infoblox network type
|
||||
'''
|
||||
check_ip = ip.split('/')
|
||||
|
||||
if validate_ip_address(check_ip[0]) and 'ipaddr' in arg_spec:
|
||||
arg_spec['ipv4addr'] = arg_spec.pop('ipaddr')
|
||||
module.params['ipv4addr'] = module.params.pop('ipaddr')
|
||||
return NIOS_IPV4_FIXED_ADDRESS, arg_spec, module
|
||||
elif validate_ip_v6_address(check_ip[0]) and 'ipaddr' in arg_spec:
|
||||
arg_spec['ipv6addr'] = arg_spec.pop('ipaddr')
|
||||
module.params['ipv6addr'] = module.params.pop('ipaddr')
|
||||
return NIOS_IPV6_FIXED_ADDRESS, arg_spec, module
|
||||
|
||||
|
||||
def main():
|
||||
''' Main entry point for module execution
|
||||
'''
|
||||
option_spec = dict(
|
||||
# one of name or num is required; enforced by the function options()
|
||||
name=dict(),
|
||||
num=dict(type='int'),
|
||||
|
||||
value=dict(required=True),
|
||||
|
||||
use_option=dict(type='bool', default=True),
|
||||
vendor_class=dict(default='DHCP')
|
||||
)
|
||||
|
||||
ib_spec = dict(
|
||||
name=dict(required=True),
|
||||
ipaddr=dict(required=True, aliases=['ipaddr'], ib_req=True),
|
||||
mac=dict(required=True, aliases=['mac'], ib_req=True),
|
||||
network=dict(required=True, aliases=['network']),
|
||||
network_view=dict(default='default', aliases=['network_view']),
|
||||
|
||||
options=dict(type='list', elements='dict', options=option_spec, transform=options),
|
||||
|
||||
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)
|
||||
|
||||
# to get the argument ipaddr
|
||||
obj_filter = dict([(k, module.params[k]) for k, v in iteritems(ib_spec) if v.get('ib_req')])
|
||||
# to modify argument based on ipaddr type i.e. IPV4/IPV6
|
||||
fixed_address_ip_type, ib_spec, module = validate_ip_addr_type(obj_filter['ipaddr'], ib_spec, module)
|
||||
|
||||
wapi = WapiModule(module)
|
||||
|
||||
result = wapi.run(fixed_address_ip_type, ib_spec)
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
344
plugins/modules/net_tools/nios/nios_host_record.py
Normal file
344
plugins/modules/net_tools/nios/nios_host_record.py
Normal file
@@ -0,0 +1,344 @@
|
||||
#!/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_host_record
|
||||
author: "Peter Sprygada (@privateip)"
|
||||
short_description: Configure Infoblox NIOS host records
|
||||
description:
|
||||
- Adds and/or removes instances of host record objects from
|
||||
Infoblox NIOS servers. This module manages NIOS C(record:host) objects
|
||||
using the Infoblox WAPI interface over REST.
|
||||
- Updates instances of host record object from Infoblox NIOS servers.
|
||||
requirements:
|
||||
- infoblox-client
|
||||
extends_documentation_fragment:
|
||||
- community.general.nios
|
||||
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Specifies the fully qualified hostname to add or remove from
|
||||
the system. User can also update the hostname as it is possible
|
||||
to pass a dict containing I(new_name), I(old_name). See examples.
|
||||
required: true
|
||||
view:
|
||||
description:
|
||||
- Sets the DNS view to associate this host record with. The DNS
|
||||
view must already be configured on the system
|
||||
required: true
|
||||
default: default
|
||||
aliases:
|
||||
- dns_view
|
||||
configure_for_dns:
|
||||
description:
|
||||
- Sets the DNS to particular parent. If user needs to bypass DNS
|
||||
user can make the value to false.
|
||||
type: bool
|
||||
required: false
|
||||
default: true
|
||||
aliases:
|
||||
- dns
|
||||
ipv4addrs:
|
||||
description:
|
||||
- Configures the IPv4 addresses for this host record. This argument
|
||||
accepts a list of values (see suboptions)
|
||||
aliases:
|
||||
- ipv4
|
||||
suboptions:
|
||||
ipv4addr:
|
||||
description:
|
||||
- Configures the IPv4 address for the host record. Users can dynamically
|
||||
allocate ipv4 address to host record by passing dictionary containing,
|
||||
I(nios_next_ip) and I(CIDR network range). If user wants to add or
|
||||
remove the ipv4 address from existing record, I(add/remove)
|
||||
params need to be used. See examples
|
||||
required: true
|
||||
aliases:
|
||||
- address
|
||||
configure_for_dhcp:
|
||||
description:
|
||||
- Configure the host_record over DHCP instead of DNS, if user
|
||||
changes it to true, user need to mention MAC address to configure
|
||||
required: false
|
||||
aliases:
|
||||
- dhcp
|
||||
mac:
|
||||
description:
|
||||
- Configures the hardware MAC address for the host record. If user makes
|
||||
DHCP to true, user need to mention MAC address.
|
||||
required: false
|
||||
aliases:
|
||||
- mac
|
||||
add:
|
||||
description:
|
||||
- If user wants to add the ipv4 address to an existing host record.
|
||||
Note that with I(add) user will have to keep the I(state) as I(present),
|
||||
as new IP address is allocated to existing host record. See examples.
|
||||
type: bool
|
||||
required: false
|
||||
aliases:
|
||||
- add
|
||||
remove:
|
||||
description:
|
||||
- If user wants to remove the ipv4 address from an existing host record.
|
||||
Note that with I(remove) user will have to change the I(state) to I(absent),
|
||||
as IP address is de-allocated from an existing host record. See examples.
|
||||
type: bool
|
||||
required: false
|
||||
aliases:
|
||||
- remove
|
||||
ipv6addrs:
|
||||
description:
|
||||
- Configures the IPv6 addresses for the host record. This argument
|
||||
accepts a list of values (see options)
|
||||
aliases:
|
||||
- ipv6
|
||||
suboptions:
|
||||
ipv6addr:
|
||||
description:
|
||||
- Configures the IPv6 address for the host record
|
||||
required: true
|
||||
aliases:
|
||||
- address
|
||||
configure_for_dhcp:
|
||||
description:
|
||||
- Configure the host_record over DHCP instead of DNS, if user
|
||||
changes it to true, user need to mention MAC address to configure
|
||||
required: false
|
||||
aliases:
|
||||
- dhcp
|
||||
aliases:
|
||||
description:
|
||||
- Configures an optional list of additional aliases to add to the host
|
||||
record. These are equivalent to CNAMEs but held within a host
|
||||
record. Must be in list format.
|
||||
ttl:
|
||||
description:
|
||||
- Configures the TTL to be associated with this host 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: configure an ipv4 host record
|
||||
nios_host_record:
|
||||
name: host.ansible.com
|
||||
ipv4:
|
||||
- address: 192.168.10.1
|
||||
aliases:
|
||||
- cname.ansible.com
|
||||
state: present
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
- name: add a comment to an existing host record
|
||||
nios_host_record:
|
||||
name: host.ansible.com
|
||||
ipv4:
|
||||
- address: 192.168.10.1
|
||||
comment: this is a test comment
|
||||
state: present
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
- name: remove a host record from the system
|
||||
nios_host_record:
|
||||
name: host.ansible.com
|
||||
state: absent
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
- name: update an ipv4 host record
|
||||
nios_host_record:
|
||||
name: {new_name: host-new.ansible.com, old_name: host.ansible.com}
|
||||
ipv4:
|
||||
- address: 192.168.10.1
|
||||
state: present
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
- name: create an ipv4 host record bypassing DNS
|
||||
nios_host_record:
|
||||
name: new_host
|
||||
ipv4:
|
||||
- address: 192.168.10.1
|
||||
dns: false
|
||||
state: present
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
- name: create an ipv4 host record over DHCP
|
||||
nios_host_record:
|
||||
name: host.ansible.com
|
||||
ipv4:
|
||||
- address: 192.168.10.1
|
||||
dhcp: true
|
||||
mac: 00-80-C8-E3-4C-BD
|
||||
state: present
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
- name: dynamically add host record to next available ip
|
||||
nios_host_record:
|
||||
name: host.ansible.com
|
||||
ipv4:
|
||||
- address: {nios_next_ip: 192.168.10.0/24}
|
||||
comment: this is a test comment
|
||||
state: present
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
- name: add ip to host record
|
||||
nios_host_record:
|
||||
name: host.ansible.com
|
||||
ipv4:
|
||||
- address: 192.168.10.2
|
||||
add: true
|
||||
state: present
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
- name: remove ip to host record
|
||||
nios_host_record:
|
||||
name: host.ansible.com
|
||||
ipv4:
|
||||
- address: 192.168.10.1
|
||||
remove: true
|
||||
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.six import iteritems
|
||||
from ansible_collections.community.general.plugins.module_utils.net_tools.nios.api import WapiModule
|
||||
from ansible_collections.community.general.plugins.module_utils.net_tools.nios.api import NIOS_HOST_RECORD
|
||||
|
||||
|
||||
def ipaddr(module, key, filtered_keys=None):
|
||||
''' Transforms the input value into a struct supported by WAPI
|
||||
This function will transform the input from the playbook into a struct
|
||||
that is valid for WAPI in the form of:
|
||||
{
|
||||
ipv4addr: <value>,
|
||||
mac: <value>
|
||||
}
|
||||
This function does not validate the values are properly formatted or in
|
||||
the acceptable range, that is left to WAPI.
|
||||
'''
|
||||
filtered_keys = filtered_keys or list()
|
||||
objects = list()
|
||||
for item in module.params[key]:
|
||||
objects.append(dict([(k, v) for k, v in iteritems(item) if v is not None and k not in filtered_keys]))
|
||||
return objects
|
||||
|
||||
|
||||
def ipv4addrs(module):
|
||||
return ipaddr(module, 'ipv4addrs', filtered_keys=['address', 'dhcp'])
|
||||
|
||||
|
||||
def ipv6addrs(module):
|
||||
return ipaddr(module, 'ipv6addrs', filtered_keys=['address', 'dhcp'])
|
||||
|
||||
|
||||
def main():
|
||||
''' Main entry point for module execution
|
||||
'''
|
||||
ipv4addr_spec = dict(
|
||||
ipv4addr=dict(required=True, aliases=['address'], ib_req=True),
|
||||
configure_for_dhcp=dict(type='bool', required=False, aliases=['dhcp'], ib_req=True),
|
||||
mac=dict(required=False, aliases=['mac'], ib_req=True),
|
||||
add=dict(type='bool', aliases=['add'], required=False),
|
||||
remove=dict(type='bool', aliases=['remove'], required=False)
|
||||
)
|
||||
|
||||
ipv6addr_spec = dict(
|
||||
ipv6addr=dict(required=True, aliases=['address'], ib_req=True),
|
||||
configure_for_dhcp=dict(type='bool', required=False, aliases=['configure_for_dhcp'], ib_req=True),
|
||||
mac=dict(required=False, aliases=['mac'], ib_req=True)
|
||||
)
|
||||
|
||||
ib_spec = dict(
|
||||
name=dict(required=True, ib_req=True),
|
||||
view=dict(default='default', aliases=['dns_view'], ib_req=True),
|
||||
|
||||
ipv4addrs=dict(type='list', aliases=['ipv4'], elements='dict', options=ipv4addr_spec, transform=ipv4addrs),
|
||||
ipv6addrs=dict(type='list', aliases=['ipv6'], elements='dict', options=ipv6addr_spec, transform=ipv6addrs),
|
||||
configure_for_dns=dict(type='bool', default=True, required=False, aliases=['dns'], ib_req=True),
|
||||
aliases=dict(type='list'),
|
||||
|
||||
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(NIOS_HOST_RECORD, ib_spec)
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
493
plugins/modules/net_tools/nios/nios_member.py
Normal file
493
plugins/modules/net_tools/nios/nios_member.py
Normal file
@@ -0,0 +1,493 @@
|
||||
#!/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_member
|
||||
author: "Krishna Vasudevan (@krisvasudevan)"
|
||||
short_description: Configure Infoblox NIOS members
|
||||
description:
|
||||
- Adds and/or removes Infoblox NIOS servers. This module manages NIOS C(member) objects using the Infoblox WAPI interface over REST.
|
||||
requirements:
|
||||
- infoblox-client
|
||||
extends_documentation_fragment:
|
||||
- community.general.nios
|
||||
|
||||
options:
|
||||
host_name:
|
||||
description:
|
||||
- Specifies the host name of the member to either add or remove from
|
||||
the NIOS instance.
|
||||
required: true
|
||||
aliases:
|
||||
- name
|
||||
vip_setting:
|
||||
description:
|
||||
- Configures the network settings for the grid member.
|
||||
required: true
|
||||
suboptions:
|
||||
address:
|
||||
description:
|
||||
- The IPv4 Address of the Grid Member
|
||||
subnet_mask:
|
||||
description:
|
||||
- The subnet mask for the Grid Member
|
||||
gateway:
|
||||
description:
|
||||
- The default gateway for the Grid Member
|
||||
ipv6_setting:
|
||||
description:
|
||||
- Configures the IPv6 settings for the grid member.
|
||||
required: true
|
||||
suboptions:
|
||||
virtual_ip:
|
||||
description:
|
||||
- The IPv6 Address of the Grid Member
|
||||
cidr_prefix:
|
||||
description:
|
||||
- The IPv6 CIDR prefix for the Grid Member
|
||||
gateway:
|
||||
description:
|
||||
- The gateway address for the Grid Member
|
||||
config_addr_type:
|
||||
description:
|
||||
- Address configuration type (IPV4/IPV6/BOTH)
|
||||
default: IPV4
|
||||
comment:
|
||||
description:
|
||||
- A descriptive comment of the Grid member.
|
||||
extattrs:
|
||||
description:
|
||||
- Extensible attributes associated with the object.
|
||||
enable_ha:
|
||||
description:
|
||||
- If set to True, the member has two physical nodes (HA pair).
|
||||
type: bool
|
||||
router_id:
|
||||
description:
|
||||
- Virtual router identifier. Provide this ID if "ha_enabled" is set to "true". This is a unique VRID number (from 1 to 255) for the local subnet.
|
||||
lan2_enabled:
|
||||
description:
|
||||
- When set to "true", the LAN2 port is enabled as an independent port or as a port for failover purposes.
|
||||
type: bool
|
||||
lan2_port_setting:
|
||||
description:
|
||||
- Settings for the Grid member LAN2 port if 'lan2_enabled' is set to "true".
|
||||
suboptions:
|
||||
enabled:
|
||||
description:
|
||||
- If set to True, then it has its own IP settings.
|
||||
type: bool
|
||||
network_setting:
|
||||
description:
|
||||
- If the 'enable' field is set to True, this defines IPv4 network settings for LAN2.
|
||||
suboptions:
|
||||
address:
|
||||
description:
|
||||
- The IPv4 Address of LAN2
|
||||
subnet_mask:
|
||||
description:
|
||||
- The subnet mask of LAN2
|
||||
gateway:
|
||||
description:
|
||||
- The default gateway of LAN2
|
||||
v6_network_setting:
|
||||
description:
|
||||
- If the 'enable' field is set to True, this defines IPv6 network settings for LAN2.
|
||||
suboptions:
|
||||
virtual_ip:
|
||||
description:
|
||||
- The IPv6 Address of LAN2
|
||||
cidr_prefix:
|
||||
description:
|
||||
- The IPv6 CIDR prefix of LAN2
|
||||
gateway:
|
||||
description:
|
||||
- The gateway address of LAN2
|
||||
platform:
|
||||
description:
|
||||
- Configures the Hardware Platform.
|
||||
default: INFOBLOX
|
||||
node_info:
|
||||
description:
|
||||
- Configures the node information list with detailed status report on the operations of the Grid Member.
|
||||
suboptions:
|
||||
lan2_physical_setting:
|
||||
description:
|
||||
- Physical port settings for the LAN2 interface.
|
||||
suboptions:
|
||||
auto_port_setting_enabled:
|
||||
description:
|
||||
- Enable or disalbe the auto port setting.
|
||||
type: bool
|
||||
duplex:
|
||||
description:
|
||||
- The port duplex; if speed is 1000, duplex must be FULL.
|
||||
speed:
|
||||
description:
|
||||
- The port speed; if speed is 1000, duplex is FULL.
|
||||
lan_ha_port_setting:
|
||||
description:
|
||||
- LAN/HA port settings for the node.
|
||||
suboptions:
|
||||
ha_ip_address:
|
||||
description:
|
||||
- HA IP address.
|
||||
ha_port_setting:
|
||||
description:
|
||||
- Physical port settings for the HA interface.
|
||||
suboptions:
|
||||
auto_port_setting_enabled:
|
||||
description:
|
||||
- Enable or disalbe the auto port setting.
|
||||
type: bool
|
||||
duplex:
|
||||
description:
|
||||
- The port duplex; if speed is 1000, duplex must be FULL.
|
||||
speed:
|
||||
description:
|
||||
- The port speed; if speed is 1000, duplex is FULL.
|
||||
lan_port_setting:
|
||||
description:
|
||||
- Physical port settings for the LAN interface.
|
||||
suboptions:
|
||||
auto_port_setting_enabled:
|
||||
description:
|
||||
- Enable or disalbe the auto port setting.
|
||||
type: bool
|
||||
duplex:
|
||||
description:
|
||||
- The port duplex; if speed is 1000, duplex must be FULL.
|
||||
speed:
|
||||
description:
|
||||
- The port speed; if speed is 1000, duplex is FULL.
|
||||
mgmt_ipv6addr:
|
||||
description:
|
||||
- Public IPv6 address for the LAN1 interface.
|
||||
mgmt_lan:
|
||||
description:
|
||||
- Public IPv4 address for the LAN1 interface.
|
||||
mgmt_network_setting:
|
||||
description:
|
||||
- Network settings for the MGMT port of the node.
|
||||
suboptions:
|
||||
address:
|
||||
description:
|
||||
- The IPv4 Address of MGMT
|
||||
subnet_mask:
|
||||
description:
|
||||
- The subnet mask of MGMT
|
||||
gateway:
|
||||
description:
|
||||
- The default gateway of MGMT
|
||||
v6_mgmt_network_setting:
|
||||
description:
|
||||
- The network settings for the IPv6 MGMT port of the node.
|
||||
suboptions:
|
||||
virtual_ip:
|
||||
description:
|
||||
- The IPv6 Address of MGMT
|
||||
cidr_prefix:
|
||||
description:
|
||||
- The IPv6 CIDR prefix of MGMT
|
||||
gateway:
|
||||
description:
|
||||
- The gateway address of MGMT
|
||||
mgmt_port_setting:
|
||||
description:
|
||||
- Settings for the member MGMT port.
|
||||
suboptions:
|
||||
enabled:
|
||||
description:
|
||||
- Determines if MGMT port settings should be enabled.
|
||||
type: bool
|
||||
security_access_enabled:
|
||||
description:
|
||||
- Determines if security access on the MGMT port is enabled or not.
|
||||
type: bool
|
||||
vpn_enabled:
|
||||
description:
|
||||
- Determines if VPN on the MGMT port is enabled or not.
|
||||
type: bool
|
||||
upgrade_group:
|
||||
description:
|
||||
- The name of the upgrade group to which this Grid member belongs.
|
||||
default: Default
|
||||
use_syslog_proxy_setting:
|
||||
description:
|
||||
- Use flag for external_syslog_server_enable , syslog_servers, syslog_proxy_setting, syslog_size
|
||||
type: bool
|
||||
external_syslog_server_enable:
|
||||
description:
|
||||
- Determines if external syslog servers should be enabled
|
||||
type: bool
|
||||
syslog_servers:
|
||||
description:
|
||||
- The list of external syslog servers.
|
||||
suboptions:
|
||||
address:
|
||||
description:
|
||||
- The server address.
|
||||
category_list:
|
||||
description:
|
||||
- The list of all syslog logging categories.
|
||||
connection_type:
|
||||
description:
|
||||
- The connection type for communicating with this server.(STCP/TCP?UDP)
|
||||
default: UDP
|
||||
local_interface:
|
||||
description:
|
||||
- The local interface through which the appliance sends syslog messages to the syslog server.(ANY/LAN/MGMT)
|
||||
default: ANY
|
||||
message_node_id:
|
||||
description:
|
||||
- Identify the node in the syslog message. (HOSTNAME/IP_HOSTNAME/LAN/MGMT)
|
||||
default: LAN
|
||||
message_source:
|
||||
description:
|
||||
- The source of syslog messages to be sent to the external syslog server.
|
||||
default: ANY
|
||||
only_category_list:
|
||||
description:
|
||||
- The list of selected syslog logging categories. The appliance forwards syslog messages that belong to the selected categories.
|
||||
type: bool
|
||||
port:
|
||||
description:
|
||||
- The port this server listens on.
|
||||
default: 514
|
||||
severity:
|
||||
description:
|
||||
- The severity filter. The appliance sends log messages of the specified severity and above to the external syslog server.
|
||||
default: DEBUG
|
||||
pre_provisioning:
|
||||
description:
|
||||
- Pre-provisioning information.
|
||||
suboptions:
|
||||
hardware_info:
|
||||
description:
|
||||
- An array of structures that describe the hardware being pre-provisioned.
|
||||
suboptions:
|
||||
hwmodel:
|
||||
description:
|
||||
- Hardware model
|
||||
hwtype:
|
||||
description:
|
||||
- Hardware type.
|
||||
licenses:
|
||||
description:
|
||||
- An array of license types.
|
||||
create_token:
|
||||
description:
|
||||
- Flag for initiating a create token request for pre-provisioned members.
|
||||
type: bool
|
||||
default: False
|
||||
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: add a member to the grid with IPv4 address
|
||||
nios_member:
|
||||
host_name: member01.localdomain
|
||||
vip_setting:
|
||||
- address: 192.168.1.100
|
||||
subnet_mask: 255.255.255.0
|
||||
gateway: 192.168.1.1
|
||||
config_addr_type: IPV4
|
||||
platform: VNIOS
|
||||
comment: "Created by Ansible"
|
||||
state: present
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
- name: add a HA member to the grid
|
||||
nios_member:
|
||||
host_name: memberha.localdomain
|
||||
vip_setting:
|
||||
- address: 192.168.1.100
|
||||
subnet_mask: 255.255.255.0
|
||||
gateway: 192.168.1.1
|
||||
config_addr_type: IPV4
|
||||
platform: VNIOS
|
||||
enable_ha: true
|
||||
router_id: 150
|
||||
node_info:
|
||||
- lan_ha_port_setting:
|
||||
- ha_ip_address: 192.168.1.70
|
||||
mgmt_lan: 192.168.1.80
|
||||
- lan_ha_port_setting:
|
||||
- ha_ip_address: 192.168.1.71
|
||||
mgmt_lan: 192.168.1.81
|
||||
comment: "Created by Ansible"
|
||||
state: present
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
- name: update the member with pre-provisioning details specified
|
||||
nios_member:
|
||||
name: member01.localdomain
|
||||
pre_provisioning:
|
||||
- hardware_info:
|
||||
- hwmodel: IB-VM-820
|
||||
hwtype: IB-VNIOS
|
||||
licenses:
|
||||
- dns
|
||||
- dhcp
|
||||
- enterprise
|
||||
- vnios
|
||||
comment: "Updated by Ansible"
|
||||
state: present
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
- name: remove the member
|
||||
nios_member:
|
||||
name: member01.localdomain
|
||||
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.six import iteritems
|
||||
from ansible_collections.community.general.plugins.module_utils.net_tools.nios.api import WapiModule
|
||||
from ansible_collections.community.general.plugins.module_utils.net_tools.nios.api import NIOS_MEMBER
|
||||
|
||||
|
||||
def main():
|
||||
''' Main entry point for module execution
|
||||
'''
|
||||
ipv4_spec = dict(
|
||||
address=dict(),
|
||||
subnet_mask=dict(),
|
||||
gateway=dict(),
|
||||
)
|
||||
|
||||
ipv6_spec = dict(
|
||||
virtual_ip=dict(),
|
||||
cidr_prefix=dict(type='int'),
|
||||
gateway=dict(),
|
||||
)
|
||||
|
||||
port_spec = dict(
|
||||
auto_port_setting_enabled=dict(type='bool'),
|
||||
duplex=dict(),
|
||||
speed=dict(),
|
||||
)
|
||||
|
||||
lan2_port_spec = dict(
|
||||
enabled=dict(type='bool'),
|
||||
network_setting=dict(type='list', elements='dict', options=ipv4_spec),
|
||||
v6_network_setting=dict(type='list', elements='dict', options=ipv6_spec),
|
||||
)
|
||||
|
||||
ha_port_spec = dict(
|
||||
ha_ip_address=dict(),
|
||||
ha_port_setting=dict(type='list', elements='dict', options=port_spec),
|
||||
lan_port_setting=dict(type='list', elements='dict', options=port_spec),
|
||||
mgmt_lan=dict(),
|
||||
mgmt_ipv6addr=dict(),
|
||||
)
|
||||
|
||||
node_spec = dict(
|
||||
lan2_physical_setting=dict(type='list', elements='dict', options=port_spec),
|
||||
lan_ha_port_setting=dict(type='list', elements='dict', options=ha_port_spec),
|
||||
mgmt_network_setting=dict(type='list', elements='dict', options=ipv4_spec),
|
||||
v6_mgmt_network_setting=dict(type='list', elements='dict', options=ipv6_spec),
|
||||
)
|
||||
|
||||
mgmt_port_spec = dict(
|
||||
enabled=dict(type='bool'),
|
||||
security_access_enabled=dict(type='bool'),
|
||||
vpn_enabled=dict(type='bool'),
|
||||
)
|
||||
|
||||
syslog_spec = dict(
|
||||
address=dict(),
|
||||
category_list=dict(type='list'),
|
||||
connection_type=dict(default='UDP'),
|
||||
local_interface=dict(default='ANY'),
|
||||
message_node_id=dict(default='LAN'),
|
||||
message_source=dict(default='ANY'),
|
||||
only_category_list=dict(type='bool'),
|
||||
port=dict(type='int', default=514),
|
||||
severity=dict(default='DEBUG'),
|
||||
)
|
||||
|
||||
hw_spec = dict(
|
||||
hwmodel=dict(),
|
||||
hwtype=dict(),
|
||||
)
|
||||
|
||||
pre_prov_spec = dict(
|
||||
hardware_info=dict(type='list', elements='dict', options=hw_spec),
|
||||
licenses=dict(type='list'),
|
||||
)
|
||||
|
||||
ib_spec = dict(
|
||||
host_name=dict(required=True, aliases=['name'], ib_req=True),
|
||||
vip_setting=dict(type='list', elements='dict', options=ipv4_spec),
|
||||
ipv6_setting=dict(type='list', elements='dict', options=ipv6_spec),
|
||||
config_addr_type=dict(default='IPV4'),
|
||||
comment=dict(),
|
||||
enable_ha=dict(type='bool', default=False),
|
||||
router_id=dict(type='int'),
|
||||
lan2_enabled=dict(type='bool', default=False),
|
||||
lan2_port_setting=dict(type='list', elements='dict', options=lan2_port_spec),
|
||||
platform=dict(default='INFOBLOX'),
|
||||
node_info=dict(type='list', elements='dict', options=node_spec),
|
||||
mgmt_port_setting=dict(type='list', elements='dict', options=mgmt_port_spec),
|
||||
upgrade_group=dict(default='Default'),
|
||||
use_syslog_proxy_setting=dict(type='bool'),
|
||||
external_syslog_server_enable=dict(type='bool'),
|
||||
syslog_servers=dict(type='list', elements='dict', options=syslog_spec),
|
||||
pre_provisioning=dict(type='list', elements='dict', options=pre_prov_spec),
|
||||
extattrs=dict(type='dict'),
|
||||
create_token=dict(type='bool', default=False),
|
||||
)
|
||||
|
||||
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(NIOS_MEMBER, ib_spec)
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
158
plugins/modules/net_tools/nios/nios_mx_record.py
Normal file
158
plugins/modules/net_tools/nios/nios_mx_record.py
Normal file
@@ -0,0 +1,158 @@
|
||||
#!/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_mx_record
|
||||
author: "Blair Rampling (@brampling)"
|
||||
short_description: Configure Infoblox NIOS MX records
|
||||
description:
|
||||
- Adds and/or removes instances of MX record objects from
|
||||
Infoblox NIOS servers. This module manages NIOS C(record:mx) objects
|
||||
using the Infoblox WAPI interface over REST.
|
||||
requirements:
|
||||
- infoblox-client
|
||||
extends_documentation_fragment:
|
||||
- community.general.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 a record with. The DNS
|
||||
view must already be configured on the system
|
||||
required: true
|
||||
default: default
|
||||
aliases:
|
||||
- dns_view
|
||||
mail_exchanger:
|
||||
description:
|
||||
- Configures the mail exchanger FQDN for this MX record.
|
||||
required: true
|
||||
aliases:
|
||||
- mx
|
||||
preference:
|
||||
description:
|
||||
- Configures the preference (0-65535) for this MX record.
|
||||
required: true
|
||||
ttl:
|
||||
description:
|
||||
- Configures the TTL to be associated with this host 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: configure an MX record
|
||||
nios_mx_record:
|
||||
name: ansible.com
|
||||
mx: mailhost.ansible.com
|
||||
preference: 0
|
||||
state: present
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
|
||||
- name: add a comment to an existing MX record
|
||||
nios_mx_record:
|
||||
name: ansible.com
|
||||
mx: mailhost.ansible.com
|
||||
preference: 0
|
||||
comment: this is a test comment
|
||||
state: present
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
|
||||
- name: remove an MX record from the system
|
||||
nios_mx_record:
|
||||
name: ansible.com
|
||||
mx: mailhost.ansible.com
|
||||
preference: 0
|
||||
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.six import iteritems
|
||||
from ansible_collections.community.general.plugins.module_utils.net_tools.nios.api import WapiModule
|
||||
from ansible_collections.community.general.plugins.module_utils.net_tools.nios.api import NIOS_MX_RECORD
|
||||
|
||||
|
||||
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),
|
||||
|
||||
mail_exchanger=dict(aliases=['mx'], ib_req=True),
|
||||
preference=dict(type='int', 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(NIOS_MX_RECORD, ib_spec)
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
192
plugins/modules/net_tools/nios/nios_naptr_record.py
Normal file
192
plugins/modules/net_tools/nios/nios_naptr_record.py
Normal file
@@ -0,0 +1,192 @@
|
||||
#!/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_naptr_record
|
||||
author: "Blair Rampling (@brampling)"
|
||||
short_description: Configure Infoblox NIOS NAPTR records
|
||||
description:
|
||||
- Adds and/or removes instances of NAPTR record objects from
|
||||
Infoblox NIOS servers. This module manages NIOS C(record:naptr) objects
|
||||
using the Infoblox WAPI interface over REST.
|
||||
requirements:
|
||||
- infoblox_client
|
||||
extends_documentation_fragment:
|
||||
- community.general.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 a record with. The DNS
|
||||
view must already be configured on the system
|
||||
required: true
|
||||
default: default
|
||||
aliases:
|
||||
- dns_view
|
||||
order:
|
||||
description:
|
||||
- Configures the order (0-65535) for this NAPTR record. This parameter
|
||||
specifies the order in which the NAPTR rules are applied when
|
||||
multiple rules are present.
|
||||
required: true
|
||||
preference:
|
||||
description:
|
||||
- Configures the preference (0-65535) for this NAPTR record. The
|
||||
preference field determines the order NAPTR records are processed
|
||||
when multiple records with the same order parameter are present.
|
||||
required: true
|
||||
replacement:
|
||||
description:
|
||||
- Configures the replacement field for this NAPTR record.
|
||||
For nonterminal NAPTR records, this field specifies the
|
||||
next domain name to look up.
|
||||
required: true
|
||||
services:
|
||||
description:
|
||||
- Configures the services field (128 characters maximum) for this
|
||||
NAPTR record. The services field contains protocol and service
|
||||
identifiers, such as "http+E2U" or "SIPS+D2T".
|
||||
required: false
|
||||
flags:
|
||||
description:
|
||||
- Configures the flags field for this NAPTR record. These control the
|
||||
interpretation of the fields for an NAPTR record object. Supported
|
||||
values for the flags field are "U", "S", "P" and "A".
|
||||
required: false
|
||||
regexp:
|
||||
description:
|
||||
- Configures the regexp field for this NAPTR record. This is the
|
||||
regular expression-based rewriting rule of the NAPTR record. This
|
||||
should be a POSIX compliant regular expression, including the
|
||||
substitution rule and flags. Refer to RFC 2915 for the field syntax
|
||||
details.
|
||||
required: false
|
||||
ttl:
|
||||
description:
|
||||
- Configures the TTL to be associated with this NAPTR 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: configure a NAPTR record
|
||||
nios_naptr_record:
|
||||
name: '*.subscriber-100.ansiblezone.com'
|
||||
order: 1000
|
||||
preference: 10
|
||||
replacement: replacement1.network.ansiblezone.com
|
||||
state: present
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
|
||||
- name: add a comment to an existing NAPTR record
|
||||
nios_naptr_record:
|
||||
name: '*.subscriber-100.ansiblezone.com'
|
||||
order: 1000
|
||||
preference: 10
|
||||
replacement: replacement1.network.ansiblezone.com
|
||||
comment: this is a test comment
|
||||
state: present
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
|
||||
- name: remove a NAPTR record from the system
|
||||
nios_naptr_record:
|
||||
name: '*.subscriber-100.ansiblezone.com'
|
||||
order: 1000
|
||||
preference: 10
|
||||
replacement: replacement1.network.ansiblezone.com
|
||||
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.six import iteritems
|
||||
from ansible_collections.community.general.plugins.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),
|
||||
|
||||
order=dict(type='int', ib_req=True),
|
||||
preference=dict(type='int', ib_req=True),
|
||||
replacement=dict(ib_req=True),
|
||||
services=dict(),
|
||||
flags=dict(),
|
||||
regexp=dict(),
|
||||
|
||||
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:naptr', ib_spec)
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
298
plugins/modules/net_tools/nios/nios_network.py
Normal file
298
plugins/modules/net_tools/nios/nios_network.py
Normal file
@@ -0,0 +1,298 @@
|
||||
#!/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_network
|
||||
author: "Peter Sprygada (@privateip)"
|
||||
short_description: Configure Infoblox NIOS network object
|
||||
description:
|
||||
- Adds and/or removes instances of network objects from
|
||||
Infoblox NIOS servers. This module manages NIOS C(network) objects
|
||||
using the Infoblox WAPI interface over REST.
|
||||
- Supports both IPV4 and IPV6 internet protocols
|
||||
requirements:
|
||||
- infoblox-client
|
||||
extends_documentation_fragment:
|
||||
- community.general.nios
|
||||
|
||||
options:
|
||||
network:
|
||||
description:
|
||||
- Specifies the network to add or remove from the system. The value
|
||||
should use CIDR notation.
|
||||
required: true
|
||||
aliases:
|
||||
- name
|
||||
- cidr
|
||||
network_view:
|
||||
description:
|
||||
- Configures the name of the network view to associate with this
|
||||
configured instance.
|
||||
required: true
|
||||
default: default
|
||||
options:
|
||||
description:
|
||||
- Configures the set of DHCP options to be included as part of
|
||||
the configured network instance. This argument accepts a list
|
||||
of values (see suboptions). When configuring suboptions at
|
||||
least one of C(name) or C(num) must be specified.
|
||||
suboptions:
|
||||
name:
|
||||
description:
|
||||
- The name of the DHCP option to configure. The standard options are
|
||||
C(router), C(router-templates), C(domain-name-servers), C(domain-name),
|
||||
C(broadcast-address), C(broadcast-address-offset), C(dhcp-lease-time),
|
||||
and C(dhcp6.name-servers).
|
||||
num:
|
||||
description:
|
||||
- The number of the DHCP option to configure
|
||||
value:
|
||||
description:
|
||||
- The value of the DHCP option specified by C(name)
|
||||
required: true
|
||||
use_option:
|
||||
description:
|
||||
- Only applies to a subset of options (see NIOS API documentation)
|
||||
type: bool
|
||||
default: 'yes'
|
||||
vendor_class:
|
||||
description:
|
||||
- The name of the space this DHCP option is associated to
|
||||
default: DHCP
|
||||
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.
|
||||
container:
|
||||
description:
|
||||
- If set to true it'll create the network container to be added or removed
|
||||
from the system.
|
||||
type: bool
|
||||
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 network ipv4
|
||||
nios_network:
|
||||
network: 192.168.10.0/24
|
||||
comment: this is a test comment
|
||||
state: present
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
- name: configure a network ipv6
|
||||
nios_network:
|
||||
network: fe80::/64
|
||||
comment: this is a test comment
|
||||
state: present
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
- name: set dhcp options for a network ipv4
|
||||
nios_network:
|
||||
network: 192.168.10.0/24
|
||||
comment: this is a test comment
|
||||
options:
|
||||
- name: domain-name
|
||||
value: ansible.com
|
||||
state: present
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
- name: remove a network ipv4
|
||||
nios_network:
|
||||
network: 192.168.10.0/24
|
||||
state: absent
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
- name: configure a ipv4 network container
|
||||
nios_network:
|
||||
network: 192.168.10.0/24
|
||||
container: true
|
||||
comment: test network container
|
||||
state: present
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
- name: configure a ipv6 network container
|
||||
nios_network:
|
||||
network: fe80::/64
|
||||
container: true
|
||||
comment: test network container
|
||||
state: present
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
- name: remove a ipv4 network container
|
||||
nios_network:
|
||||
networkr: 192.168.10.0/24
|
||||
container: true
|
||||
comment: test network container
|
||||
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.six import iteritems
|
||||
from ansible_collections.community.general.plugins.module_utils.net_tools.nios.api import WapiModule
|
||||
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import validate_ip_address, validate_ip_v6_address
|
||||
from ansible_collections.community.general.plugins.module_utils.net_tools.nios.api import NIOS_IPV4_NETWORK, NIOS_IPV6_NETWORK
|
||||
from ansible_collections.community.general.plugins.module_utils.net_tools.nios.api import NIOS_IPV4_NETWORK_CONTAINER, NIOS_IPV6_NETWORK_CONTAINER
|
||||
|
||||
|
||||
def options(module):
|
||||
''' Transforms the module argument into a valid WAPI struct
|
||||
This function will transform the options argument into a structure that
|
||||
is a valid WAPI structure in the format of:
|
||||
{
|
||||
name: <value>,
|
||||
num: <value>,
|
||||
value: <value>,
|
||||
use_option: <value>,
|
||||
vendor_class: <value>
|
||||
}
|
||||
It will remove any options that are set to None since WAPI will error on
|
||||
that condition. It will also verify that either `name` or `num` is
|
||||
set in the structure but does not validate the values are equal.
|
||||
The remainder of the value validation is performed by WAPI
|
||||
'''
|
||||
options = list()
|
||||
for item in module.params['options']:
|
||||
opt = dict([(k, v) for k, v in iteritems(item) if v is not None])
|
||||
if 'name' not in opt and 'num' not in opt:
|
||||
module.fail_json(msg='one of `name` or `num` is required for option value')
|
||||
options.append(opt)
|
||||
return options
|
||||
|
||||
|
||||
def check_ip_addr_type(obj_filter, ib_spec):
|
||||
'''This function will check if the argument ip is type v4/v6 and return appropriate infoblox
|
||||
network/networkcontainer type
|
||||
'''
|
||||
|
||||
ip = obj_filter['network']
|
||||
if 'container' in obj_filter and obj_filter['container']:
|
||||
check_ip = ip.split('/')
|
||||
del ib_spec['container'] # removing the container key from post arguments
|
||||
del ib_spec['options'] # removing option argument as for network container it's not supported
|
||||
if validate_ip_address(check_ip[0]):
|
||||
return NIOS_IPV4_NETWORK_CONTAINER, ib_spec
|
||||
elif validate_ip_v6_address(check_ip[0]):
|
||||
return NIOS_IPV6_NETWORK_CONTAINER, ib_spec
|
||||
else:
|
||||
check_ip = ip.split('/')
|
||||
del ib_spec['container'] # removing the container key from post arguments
|
||||
if validate_ip_address(check_ip[0]):
|
||||
return NIOS_IPV4_NETWORK, ib_spec
|
||||
elif validate_ip_v6_address(check_ip[0]):
|
||||
return NIOS_IPV6_NETWORK, ib_spec
|
||||
|
||||
|
||||
def check_vendor_specific_dhcp_option(module, ib_spec):
|
||||
'''This function will check if the argument dhcp option belongs to vendor-specific and if yes then will remove
|
||||
use_options flag which is not supported with vendor-specific dhcp options.
|
||||
'''
|
||||
for key, value in iteritems(ib_spec):
|
||||
if isinstance(module.params[key], list):
|
||||
temp_dict = module.params[key][0]
|
||||
if 'num' in temp_dict:
|
||||
if temp_dict['num'] in (43, 124, 125):
|
||||
del module.params[key][0]['use_option']
|
||||
return ib_spec
|
||||
|
||||
|
||||
def main():
|
||||
''' Main entry point for module execution
|
||||
'''
|
||||
option_spec = dict(
|
||||
# one of name or num is required; enforced by the function options()
|
||||
name=dict(),
|
||||
num=dict(type='int'),
|
||||
|
||||
value=dict(required=True),
|
||||
|
||||
use_option=dict(type='bool', default=True),
|
||||
vendor_class=dict(default='DHCP')
|
||||
)
|
||||
|
||||
ib_spec = dict(
|
||||
network=dict(required=True, aliases=['name', 'cidr'], ib_req=True),
|
||||
network_view=dict(default='default', ib_req=True),
|
||||
|
||||
options=dict(type='list', elements='dict', options=option_spec, transform=options),
|
||||
|
||||
extattrs=dict(type='dict'),
|
||||
comment=dict(),
|
||||
container=dict(type='bool', ib_req=True)
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
# to get the argument ipaddr
|
||||
obj_filter = dict([(k, module.params[k]) for k, v in iteritems(ib_spec) if v.get('ib_req')])
|
||||
network_type, ib_spec = check_ip_addr_type(obj_filter, ib_spec)
|
||||
|
||||
wapi = WapiModule(module)
|
||||
# to check for vendor specific dhcp option
|
||||
ib_spec = check_vendor_specific_dhcp_option(module, ib_spec)
|
||||
|
||||
result = wapi.run(network_type, ib_spec)
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
133
plugins/modules/net_tools/nios/nios_network_view.py
Normal file
133
plugins/modules/net_tools/nios/nios_network_view.py
Normal file
@@ -0,0 +1,133 @@
|
||||
#!/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_network_view
|
||||
author: "Peter Sprygada (@privateip)"
|
||||
short_description: Configure Infoblox NIOS network views
|
||||
description:
|
||||
- Adds and/or removes instances of network view objects from
|
||||
Infoblox NIOS servers. This module manages NIOS C(networkview) objects
|
||||
using the Infoblox WAPI interface over REST.
|
||||
- Updates instances of network view object from Infoblox NIOS servers.
|
||||
requirements:
|
||||
- infoblox-client
|
||||
extends_documentation_fragment:
|
||||
- community.general.nios
|
||||
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Specifies the fully qualified hostname to add or remove from
|
||||
the system. User can also update the hostname as it is possible
|
||||
to pass a dict containing I(new_name), I(old_name). See examples.
|
||||
required: true
|
||||
aliases:
|
||||
- network_view
|
||||
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 new network view
|
||||
nios_network_view:
|
||||
name: ansible
|
||||
state: present
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
- name: update the comment for network view
|
||||
nios_network_view:
|
||||
name: ansible
|
||||
comment: this is an example comment
|
||||
state: present
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
- name: remove the network view
|
||||
nios_network_view:
|
||||
name: ansible
|
||||
state: absent
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
- name: update a existing network view
|
||||
nios_network_view:
|
||||
name: {new_name: ansible-new, old_name: ansible}
|
||||
state: present
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
'''
|
||||
|
||||
RETURN = ''' # '''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible_collections.community.general.plugins.module_utils.net_tools.nios.api import WapiModule
|
||||
from ansible_collections.community.general.plugins.module_utils.net_tools.nios.api import NIOS_NETWORK_VIEW
|
||||
|
||||
|
||||
def main():
|
||||
''' Main entry point for module execution
|
||||
'''
|
||||
ib_spec = dict(
|
||||
name=dict(required=True, aliases=['network_view'], ib_req=True),
|
||||
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(NIOS_NETWORK_VIEW, ib_spec)
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
350
plugins/modules/net_tools/nios/nios_nsgroup.py
Normal file
350
plugins/modules/net_tools/nios/nios_nsgroup.py
Normal file
@@ -0,0 +1,350 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# 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_nsgroup
|
||||
short_description: Configure InfoBlox DNS Nameserver Groups
|
||||
extends_documentation_fragment:
|
||||
- community.general.nios
|
||||
|
||||
author:
|
||||
- Erich Birngruber (@ebirn)
|
||||
- Sumit Jaiswal (@sjaiswal)
|
||||
description:
|
||||
- Adds and/or removes nameserver groups form Infoblox NIOS servers.
|
||||
This module manages NIOS C(nsgroup) objects using the Infoblox. WAPI interface over REST.
|
||||
requirements:
|
||||
- infoblox_client
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Specifies the name of the NIOS nameserver group to be managed.
|
||||
required: true
|
||||
grid_primary:
|
||||
description:
|
||||
- This host is to be used as primary server in this nameserver group. It must be a grid member.
|
||||
This option is required when setting I(use_external_primaries) to C(false).
|
||||
suboptions:
|
||||
name:
|
||||
description:
|
||||
- Provide the name of the grid member to identify the host.
|
||||
required: true
|
||||
enable_preferred_primaries:
|
||||
description:
|
||||
- This flag represents whether the preferred_primaries field values of this member are used (see Infoblox WAPI docs).
|
||||
default: false
|
||||
type: bool
|
||||
grid_replicate:
|
||||
description:
|
||||
- Use DNS zone transfers if set to C(True) or ID Grid Replication if set to C(False).
|
||||
type: bool
|
||||
default: false
|
||||
lead:
|
||||
description:
|
||||
- This flag controls if the grid lead secondary nameserver performs zone transfers to non lead secondaries.
|
||||
type: bool
|
||||
default: false
|
||||
stealth:
|
||||
description:
|
||||
- Configure the external nameserver as stealth server (without NS record) in the zones.
|
||||
type: bool
|
||||
default: false
|
||||
grid_secondaries:
|
||||
description:
|
||||
- Configures the list of grid member hosts that act as secondary nameservers.
|
||||
This option is required when setting I(use_external_primaries) to C(true).
|
||||
suboptions:
|
||||
name:
|
||||
description:
|
||||
- Provide the name of the grid member to identify the host.
|
||||
required: true
|
||||
enable_preferred_primaries:
|
||||
description:
|
||||
- This flag represents whether the preferred_primaries field values of this member are used (see Infoblox WAPI docs).
|
||||
default: false
|
||||
type: bool
|
||||
grid_replicate:
|
||||
description:
|
||||
- Use DNS zone transfers if set to C(True) or ID Grid Replication if set to C(False)
|
||||
type: bool
|
||||
default: false
|
||||
lead:
|
||||
description:
|
||||
- This flag controls if the grid lead secondary nameserver performs zone transfers to non lead secondaries.
|
||||
type: bool
|
||||
default: false
|
||||
stealth:
|
||||
description:
|
||||
- Configure the external nameserver as stealth server (without NS record) in the zones.
|
||||
type: bool
|
||||
default: false
|
||||
preferred_primaries:
|
||||
description:
|
||||
- Provide a list of elements like in I(external_primaries) to set the precedence of preferred primary nameservers.
|
||||
is_grid_default:
|
||||
description:
|
||||
- If set to C(True) this nsgroup will become the default nameserver group for new zones.
|
||||
type: bool
|
||||
required: false
|
||||
default: false
|
||||
use_external_primary:
|
||||
description:
|
||||
- This flag controls whether the group is using an external primary nameserver.
|
||||
Note that modification of this field requires passing values for I(grid_secondaries) and I(external_primaries).
|
||||
type: bool
|
||||
required: false
|
||||
default: false
|
||||
external_primaries:
|
||||
description:
|
||||
- Configures a list of external nameservers (non-members of the grid).
|
||||
This option is required when setting I(use_external_primaries) to C(true).
|
||||
suboptions:
|
||||
address:
|
||||
description:
|
||||
- Configures the IP address of the external nameserver
|
||||
required: true
|
||||
name:
|
||||
description:
|
||||
- Set a label for the external nameserver
|
||||
required: true
|
||||
stealth:
|
||||
description:
|
||||
- Configure the external nameserver as stealth server (without NS record) in the zones.
|
||||
type: bool
|
||||
default: false
|
||||
tsig_key_name:
|
||||
description:
|
||||
- Sets a label for the I(tsig_key) value
|
||||
tsig_key_alg:
|
||||
description:
|
||||
- Provides the algorithm used for the I(tsig_key) in use.
|
||||
choices: ['HMAC-MD5', 'HMAC-SHA256']
|
||||
default: 'HMAC-MD5'
|
||||
tsig_key:
|
||||
description:
|
||||
- Set a DNS TSIG key for the nameserver to secure zone transfers (AFXRs).
|
||||
required: false
|
||||
external_secondaries:
|
||||
description:
|
||||
- Allows to provide a list of external secondary nameservers, that are not members of the grid.
|
||||
suboptions:
|
||||
address:
|
||||
description:
|
||||
- Configures the IP address of the external nameserver
|
||||
required: true
|
||||
name:
|
||||
description:
|
||||
- Set a label for the external nameserver
|
||||
required: true
|
||||
stealth:
|
||||
description:
|
||||
- Configure the external nameserver as stealth server (without NS record) in the zones.
|
||||
type: bool
|
||||
default: false
|
||||
tsig_key_name:
|
||||
description:
|
||||
- Sets a label for the I(tsig_key) value
|
||||
tsig_key_alg:
|
||||
description:
|
||||
- Provides the algorithm used for the I(tsig_key) in use.
|
||||
choices: ['HMAC-MD5', 'HMAC-SHA256']
|
||||
default: 'HMAC-MD5'
|
||||
tsig_key:
|
||||
description:
|
||||
- Set a DNS TSIG key for the nameserver to secure zone transfers (AFXRs).
|
||||
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.
|
||||
required: false
|
||||
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.
|
||||
required: false
|
||||
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.
|
||||
choices: [present, absent]
|
||||
default: present
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: create simple infoblox nameserver group
|
||||
nios_nsgroup:
|
||||
name: my-simple-group
|
||||
comment: "this is a simple nameserver group"
|
||||
grid_primary:
|
||||
- name: infoblox-test.example.com
|
||||
state: present
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
|
||||
- name: create infoblox nameserver group with external primaries
|
||||
nios_nsgroup:
|
||||
name: my-example-group
|
||||
use_external_primary: true
|
||||
comment: "this is my example nameserver group"
|
||||
external_primaries: "{{ ext_nameservers }}"
|
||||
grid_secondaries:
|
||||
- name: infoblox-test.example.com
|
||||
lead: True
|
||||
preferred_primaries: "{{ ext_nameservers }}"
|
||||
state: present
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
|
||||
- name: delete infoblox nameserver group
|
||||
nios_nsgroup:
|
||||
name: my-simple-group
|
||||
comment: "this is a simple nameserver group"
|
||||
grid_primary:
|
||||
- name: infoblox-test.example.com
|
||||
state: absent
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
'''
|
||||
|
||||
RETURN = ''' # '''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible_collections.community.general.plugins.module_utils.net_tools.nios.api import WapiModule
|
||||
from ansible_collections.community.general.plugins.module_utils.net_tools.nios.api import NIOS_NSGROUP
|
||||
|
||||
|
||||
# from infoblox documentation
|
||||
# Fields List
|
||||
# Field Type Req R/O Base Search
|
||||
# comment String N N Y : = ~
|
||||
# extattrs Extattr N N N ext
|
||||
# external_primaries [struct] N N N N/A
|
||||
# external_secondaries [struct] N N N N/A
|
||||
# grid_primary [struct] N N N N/A
|
||||
# grid_secondaries [struct] N N N N/A
|
||||
# is_grid_default Bool N N N N/A
|
||||
# is_multimaster Bool N Y N N/A
|
||||
# name String Y N Y : = ~
|
||||
# use_external_primary Bool N N N N/A
|
||||
|
||||
|
||||
def main():
|
||||
'''entrypoint for module execution.'''
|
||||
argument_spec = dict(
|
||||
provider=dict(required=True),
|
||||
state=dict(default='present', choices=['present', 'absent']),
|
||||
)
|
||||
|
||||
# cleanup tsig fields
|
||||
def clean_tsig(ext):
|
||||
if 'tsig_key' in ext and not ext['tsig_key']:
|
||||
del ext['tsig_key']
|
||||
if 'tsig_key' not in ext and 'tsig_key_name' in ext and not ext['tsig_key_name']:
|
||||
del ext['tsig_key_name']
|
||||
if 'tsig_key' not in ext and 'tsig_key_alg' in ext:
|
||||
del ext['tsig_key_alg']
|
||||
|
||||
def clean_grid_member(member):
|
||||
if member['preferred_primaries']:
|
||||
for ext in member['preferred_primaries']:
|
||||
clean_tsig(ext)
|
||||
if member['enable_preferred_primaries'] is False:
|
||||
del member['enable_preferred_primaries']
|
||||
del member['preferred_primaries']
|
||||
if member['lead'] is False:
|
||||
del member['lead']
|
||||
if member['grid_replicate'] is False:
|
||||
del member['grid_replicate']
|
||||
|
||||
def ext_primaries_transform(module):
|
||||
if module.params['external_primaries']:
|
||||
for ext in module.params['external_primaries']:
|
||||
clean_tsig(ext)
|
||||
return module.params['external_primaries']
|
||||
|
||||
def ext_secondaries_transform(module):
|
||||
if module.params['external_secondaries']:
|
||||
for ext in module.params['external_secondaries']:
|
||||
clean_tsig(ext)
|
||||
return module.params['external_secondaries']
|
||||
|
||||
def grid_primary_preferred_transform(module):
|
||||
for member in module.params['grid_primary']:
|
||||
clean_grid_member(member)
|
||||
return module.params['grid_primary']
|
||||
|
||||
def grid_secondaries_preferred_primaries_transform(module):
|
||||
for member in module.params['grid_secondaries']:
|
||||
clean_grid_member(member)
|
||||
return module.params['grid_secondaries']
|
||||
|
||||
extserver_spec = dict(
|
||||
address=dict(required=True, ib_req=True),
|
||||
name=dict(required=True, ib_req=True),
|
||||
stealth=dict(type='bool', default=False),
|
||||
tsig_key=dict(),
|
||||
tsig_key_alg=dict(choices=['HMAC-MD5', 'HMAC-SHA256'], default='HMAC-MD5'),
|
||||
tsig_key_name=dict(required=True)
|
||||
)
|
||||
|
||||
memberserver_spec = dict(
|
||||
name=dict(required=True, ib_req=True),
|
||||
enable_preferred_primaries=dict(type='bool', default=False),
|
||||
grid_replicate=dict(type='bool', default=False),
|
||||
lead=dict(type='bool', default=False),
|
||||
preferred_primaries=dict(type='list', elements='dict', options=extserver_spec, default=[]),
|
||||
stealth=dict(type='bool', default=False),
|
||||
)
|
||||
|
||||
ib_spec = dict(
|
||||
name=dict(required=True, ib_req=True),
|
||||
grid_primary=dict(type='list', elements='dict', options=memberserver_spec,
|
||||
transform=grid_primary_preferred_transform),
|
||||
grid_secondaries=dict(type='list', elements='dict', options=memberserver_spec,
|
||||
transform=grid_secondaries_preferred_primaries_transform),
|
||||
external_primaries=dict(type='list', elements='dict', options=extserver_spec, transform=ext_primaries_transform),
|
||||
external_secondaries=dict(type='list', elements='dict', options=extserver_spec,
|
||||
transform=ext_secondaries_transform),
|
||||
is_grid_default=dict(type='bool', default=False),
|
||||
use_external_primary=dict(type='bool', default=False),
|
||||
extattrs=dict(),
|
||||
comment=dict(),
|
||||
)
|
||||
|
||||
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(NIOS_NSGROUP, ib_spec)
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
160
plugins/modules/net_tools/nios/nios_ptr_record.py
Normal file
160
plugins/modules/net_tools/nios/nios_ptr_record.py
Normal file
@@ -0,0 +1,160 @@
|
||||
#!/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_ptr_record
|
||||
author: "Trebuchet Clement (@clementtrebuchet)"
|
||||
short_description: Configure Infoblox NIOS PTR records
|
||||
description:
|
||||
- Adds and/or removes instances of PTR record objects from
|
||||
Infoblox NIOS servers. This module manages NIOS C(record:ptr) objects
|
||||
using the Infoblox WAPI interface over REST.
|
||||
requirements:
|
||||
- infoblox_client
|
||||
extends_documentation_fragment:
|
||||
- community.general.nios
|
||||
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- The name of the DNS PTR record in FQDN format to add or remove from
|
||||
the system.
|
||||
The field is required only for an PTR object in Forward Mapping Zone.
|
||||
required: false
|
||||
view:
|
||||
description:
|
||||
- Sets the DNS view to associate this a record with. The DNS
|
||||
view must already be configured on the system
|
||||
required: false
|
||||
aliases:
|
||||
- dns_view
|
||||
ipv4addr:
|
||||
description:
|
||||
- The IPv4 Address of the record. Mutually exclusive with the ipv6addr.
|
||||
required: true
|
||||
aliases:
|
||||
- ipv4
|
||||
ipv6addr:
|
||||
description:
|
||||
- The IPv6 Address of the record. Mutually exclusive with the ipv4addr.
|
||||
required: true
|
||||
aliases:
|
||||
- ipv6
|
||||
ptrdname:
|
||||
description:
|
||||
- The domain name of the DNS PTR record in FQDN format.
|
||||
required: true
|
||||
ttl:
|
||||
description:
|
||||
- Time To Live (TTL) value for the record.
|
||||
A 32-bit unsigned integer that represents the duration, in seconds, that the record is valid (cached).
|
||||
Zero indicates that the record should not be cached.
|
||||
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. Maximum 256 characters.
|
||||
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: Create a PTR Record
|
||||
nios_ptr_record:
|
||||
ipv4: 192.168.10.1
|
||||
ptrdname: host.ansible.com
|
||||
state: present
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
|
||||
- name: Delete a PTR Record
|
||||
nios_ptr_record:
|
||||
ipv4: 192.168.10.1
|
||||
ptrdname: host.ansible.com
|
||||
state: absent
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
'''
|
||||
|
||||
RETURN = ''' # '''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible_collections.community.general.plugins.module_utils.net_tools.nios.api import WapiModule
|
||||
from ansible_collections.community.general.plugins.module_utils.net_tools.nios.api import NIOS_PTR_RECORD
|
||||
|
||||
|
||||
def main():
|
||||
# Module entry point
|
||||
ib_spec = dict(
|
||||
name=dict(required=False),
|
||||
view=dict(aliases=['dns_view'], ib_req=True),
|
||||
ipv4addr=dict(aliases=['ipv4'], ib_req=True),
|
||||
ipv6addr=dict(aliases=['ipv6'], ib_req=True),
|
||||
ptrdname=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)
|
||||
|
||||
mutually_exclusive = [('ipv4addr', 'ipv6addr')]
|
||||
required_one_of = [
|
||||
['ipv4addr', 'ipv6addr']
|
||||
]
|
||||
|
||||
module = AnsibleModule(argument_spec=argument_spec,
|
||||
mutually_exclusive=mutually_exclusive,
|
||||
supports_check_mode=True,
|
||||
required_one_of=required_one_of)
|
||||
|
||||
if module.params['ipv4addr']:
|
||||
del ib_spec['ipv6addr']
|
||||
elif module.params['ipv6addr']:
|
||||
del ib_spec['ipv4addr']
|
||||
|
||||
wapi = WapiModule(module)
|
||||
result = wapi.run(NIOS_PTR_RECORD, ib_spec)
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
172
plugins/modules/net_tools/nios/nios_srv_record.py
Normal file
172
plugins/modules/net_tools/nios/nios_srv_record.py
Normal file
@@ -0,0 +1,172 @@
|
||||
#!/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_srv_record
|
||||
author: "Blair Rampling (@brampling)"
|
||||
short_description: Configure Infoblox NIOS SRV records
|
||||
description:
|
||||
- Adds and/or removes instances of SRV record objects from
|
||||
Infoblox NIOS servers. This module manages NIOS C(record:srv) objects
|
||||
using the Infoblox WAPI interface over REST.
|
||||
requirements:
|
||||
- infoblox-client
|
||||
extends_documentation_fragment:
|
||||
- community.general.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 a record with. The DNS
|
||||
view must already be configured on the system
|
||||
required: true
|
||||
default: default
|
||||
aliases:
|
||||
- dns_view
|
||||
port:
|
||||
description:
|
||||
- Configures the port (0-65535) of this SRV record.
|
||||
required: true
|
||||
priority:
|
||||
description:
|
||||
- Configures the priority (0-65535) for this SRV record.
|
||||
required: true
|
||||
target:
|
||||
description:
|
||||
- Configures the target FQDN for this SRV record.
|
||||
required: true
|
||||
weight:
|
||||
description:
|
||||
- Configures the weight (0-65535) for this SRV record.
|
||||
required: true
|
||||
ttl:
|
||||
description:
|
||||
- Configures the TTL to be associated with this host 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: configure an SRV record
|
||||
nios_srv_record:
|
||||
name: _sip._tcp.service.ansible.com
|
||||
port: 5080
|
||||
priority: 10
|
||||
target: service1.ansible.com
|
||||
weight: 10
|
||||
state: present
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
|
||||
- name: add a comment to an existing SRV record
|
||||
nios_srv_record:
|
||||
name: _sip._tcp.service.ansible.com
|
||||
port: 5080
|
||||
priority: 10
|
||||
target: service1.ansible.com
|
||||
weight: 10
|
||||
comment: this is a test comment
|
||||
state: present
|
||||
provider:
|
||||
host: "{{ inventory_hostname_short }}"
|
||||
username: admin
|
||||
password: admin
|
||||
connection: local
|
||||
|
||||
- name: remove an SRV record from the system
|
||||
nios_srv_record:
|
||||
name: _sip._tcp.service.ansible.com
|
||||
port: 5080
|
||||
priority: 10
|
||||
target: service1.ansible.com
|
||||
weight: 10
|
||||
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.six import iteritems
|
||||
from ansible_collections.community.general.plugins.module_utils.net_tools.nios.api import WapiModule
|
||||
from ansible_collections.community.general.plugins.module_utils.net_tools.nios.api import NIOS_SRV_RECORD
|
||||
|
||||
|
||||
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),
|
||||
|
||||
port=dict(type='int', ib_req=True),
|
||||
priority=dict(type='int', ib_req=True),
|
||||
target=dict(ib_req=True),
|
||||
weight=dict(type='int', 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(NIOS_SRV_RECORD, ib_spec)
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
135
plugins/modules/net_tools/nios/nios_txt_record.py
Normal file
135
plugins/modules/net_tools/nios/nios_txt_record.py
Normal file
@@ -0,0 +1,135 @@
|
||||
#!/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
|
||||
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:
|
||||
- community.general.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_collections.community.general.plugins.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()
|
||||
226
plugins/modules/net_tools/nios/nios_zone.py
Normal file
226
plugins/modules/net_tools/nios/nios_zone.py
Normal file
@@ -0,0 +1,226 @@
|
||||
#!/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
|
||||
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:
|
||||
- community.general.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:
|
||||
description:
|
||||
- Configures the name server group for this zone. Name server group is
|
||||
mutually exclusive with grid primary and grid secondaries.
|
||||
restart_if_needed:
|
||||
description:
|
||||
- If set to true, causes the NIOS DNS service to restart and load the
|
||||
new zone configuration
|
||||
type: bool
|
||||
zone_format:
|
||||
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_collections.community.general.plugins.module_utils.net_tools.nios.api import WapiModule
|
||||
from ansible_collections.community.general.plugins.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()
|
||||
1547
plugins/modules/net_tools/nmcli.py
Normal file
1547
plugins/modules/net_tools/nmcli.py
Normal file
File diff suppressed because it is too large
Load Diff
474
plugins/modules/net_tools/nsupdate.py
Normal file
474
plugins/modules/net_tools/nsupdate.py
Normal file
@@ -0,0 +1,474 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
# (c) 2016, Marcin Skarbek <github@skarbek.name>
|
||||
# (c) 2016, Andreas Olsson <andreas@arrakis.se>
|
||||
# (c) 2017, Loic Blot <loic.blot@unix-experience.fr>
|
||||
#
|
||||
# This module was ported from https://github.com/mskarbek/ansible-nsupdate
|
||||
#
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: nsupdate
|
||||
|
||||
short_description: Manage DNS records.
|
||||
description:
|
||||
- Create, update and remove DNS records using DDNS updates
|
||||
requirements:
|
||||
- dnspython
|
||||
author: "Loic Blot (@nerzhul)"
|
||||
options:
|
||||
state:
|
||||
description:
|
||||
- Manage DNS record.
|
||||
choices: ['present', 'absent']
|
||||
default: 'present'
|
||||
server:
|
||||
description:
|
||||
- Apply DNS modification on this server, specified by IPv4 or IPv6 address.
|
||||
required: true
|
||||
port:
|
||||
description:
|
||||
- Use this TCP port when connecting to C(server).
|
||||
default: 53
|
||||
key_name:
|
||||
description:
|
||||
- Use TSIG key name to authenticate against DNS C(server)
|
||||
key_secret:
|
||||
description:
|
||||
- Use TSIG key secret, associated with C(key_name), to authenticate against C(server)
|
||||
key_algorithm:
|
||||
description:
|
||||
- Specify key algorithm used by C(key_secret).
|
||||
choices: ['HMAC-MD5.SIG-ALG.REG.INT', 'hmac-md5', 'hmac-sha1', 'hmac-sha224', 'hmac-sha256', 'hmac-sha384',
|
||||
'hmac-sha512']
|
||||
default: 'hmac-md5'
|
||||
zone:
|
||||
description:
|
||||
- DNS record will be modified on this C(zone).
|
||||
- When omitted DNS will be queried to attempt finding the correct zone.
|
||||
- Starting with Ansible 2.7 this parameter is optional.
|
||||
record:
|
||||
description:
|
||||
- Sets the DNS record to modify. When zone is omitted this has to be absolute (ending with a dot).
|
||||
required: true
|
||||
type:
|
||||
description:
|
||||
- Sets the record type.
|
||||
default: 'A'
|
||||
ttl:
|
||||
description:
|
||||
- Sets the record TTL.
|
||||
default: 3600
|
||||
value:
|
||||
description:
|
||||
- Sets the record value.
|
||||
protocol:
|
||||
description:
|
||||
- Sets the transport protocol (TCP or UDP). TCP is the recommended and a more robust option.
|
||||
default: 'tcp'
|
||||
choices: ['tcp', 'udp']
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Add or modify ansible.example.org A to 192.168.1.1"
|
||||
nsupdate:
|
||||
key_name: "nsupdate"
|
||||
key_secret: "+bFQtBCta7j2vWkjPkAFtgA=="
|
||||
server: "10.1.1.1"
|
||||
zone: "example.org"
|
||||
record: "ansible"
|
||||
value: "192.168.1.1"
|
||||
|
||||
- name: Add or modify ansible.example.org A to 192.168.1.1, 192.168.1.2 and 192.168.1.3"
|
||||
nsupdate:
|
||||
key_name: "nsupdate"
|
||||
key_secret: "+bFQtBCta7j2vWkjPkAFtgA=="
|
||||
server: "10.1.1.1"
|
||||
zone: "example.org"
|
||||
record: "ansible"
|
||||
value: ["192.168.1.1", "192.168.1.2", "192.168.1.3"]
|
||||
|
||||
- name: Remove puppet.example.org CNAME
|
||||
nsupdate:
|
||||
key_name: "nsupdate"
|
||||
key_secret: "+bFQtBCta7j2vWkjPkAFtgA=="
|
||||
server: "10.1.1.1"
|
||||
zone: "example.org"
|
||||
record: "puppet"
|
||||
type: "CNAME"
|
||||
state: absent
|
||||
|
||||
- name: Add 1.1.168.192.in-addr.arpa. PTR for ansible.example.org
|
||||
nsupdate:
|
||||
key_name: "nsupdate"
|
||||
key_secret: "+bFQtBCta7j2vWkjPkAFtgA=="
|
||||
server: "10.1.1.1"
|
||||
record: "1.1.168.192.in-addr.arpa."
|
||||
type: "PTR"
|
||||
value: "ansible.example.org."
|
||||
state: present
|
||||
|
||||
- name: Remove 1.1.168.192.in-addr.arpa. PTR
|
||||
nsupdate:
|
||||
key_name: "nsupdate"
|
||||
key_secret: "+bFQtBCta7j2vWkjPkAFtgA=="
|
||||
server: "10.1.1.1"
|
||||
record: "1.1.168.192.in-addr.arpa."
|
||||
type: "PTR"
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
changed:
|
||||
description: If module has modified record
|
||||
returned: success
|
||||
type: str
|
||||
record:
|
||||
description: DNS record
|
||||
returned: success
|
||||
type: str
|
||||
sample: 'ansible'
|
||||
ttl:
|
||||
description: DNS record TTL
|
||||
returned: success
|
||||
type: int
|
||||
sample: 86400
|
||||
type:
|
||||
description: DNS record type
|
||||
returned: success
|
||||
type: str
|
||||
sample: 'CNAME'
|
||||
value:
|
||||
description: DNS record value(s)
|
||||
returned: success
|
||||
type: list
|
||||
sample: '192.168.1.1'
|
||||
zone:
|
||||
description: DNS record zone
|
||||
returned: success
|
||||
type: str
|
||||
sample: 'example.org.'
|
||||
dns_rc:
|
||||
description: dnspython return code
|
||||
returned: always
|
||||
type: int
|
||||
sample: 4
|
||||
dns_rc_str:
|
||||
description: dnspython return code (string representation)
|
||||
returned: always
|
||||
type: str
|
||||
sample: 'REFUSED'
|
||||
'''
|
||||
|
||||
import traceback
|
||||
|
||||
from binascii import Error as binascii_error
|
||||
from socket import error as socket_error
|
||||
|
||||
DNSPYTHON_IMP_ERR = None
|
||||
try:
|
||||
import dns.update
|
||||
import dns.query
|
||||
import dns.tsigkeyring
|
||||
import dns.message
|
||||
import dns.resolver
|
||||
|
||||
HAVE_DNSPYTHON = True
|
||||
except ImportError:
|
||||
DNSPYTHON_IMP_ERR = traceback.format_exc()
|
||||
HAVE_DNSPYTHON = False
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
from ansible.module_utils._text import to_native
|
||||
|
||||
|
||||
class RecordManager(object):
|
||||
def __init__(self, module):
|
||||
self.module = module
|
||||
|
||||
if module.params['key_name']:
|
||||
try:
|
||||
self.keyring = dns.tsigkeyring.from_text({
|
||||
module.params['key_name']: module.params['key_secret']
|
||||
})
|
||||
except TypeError:
|
||||
module.fail_json(msg='Missing key_secret')
|
||||
except binascii_error as e:
|
||||
module.fail_json(msg='TSIG key error: %s' % to_native(e))
|
||||
else:
|
||||
self.keyring = None
|
||||
|
||||
if module.params['key_algorithm'] == 'hmac-md5':
|
||||
self.algorithm = 'HMAC-MD5.SIG-ALG.REG.INT'
|
||||
else:
|
||||
self.algorithm = module.params['key_algorithm']
|
||||
|
||||
if module.params['zone'] is None:
|
||||
if module.params['record'][-1] != '.':
|
||||
self.module.fail_json(msg='record must be absolute when omitting zone parameter')
|
||||
self.zone = self.lookup_zone()
|
||||
else:
|
||||
self.zone = module.params['zone']
|
||||
|
||||
if self.zone[-1] != '.':
|
||||
self.zone += '.'
|
||||
|
||||
if module.params['record'][-1] != '.':
|
||||
self.fqdn = module.params['record'] + '.' + self.zone
|
||||
else:
|
||||
self.fqdn = module.params['record']
|
||||
|
||||
if self.module.params['type'].lower() == 'txt' and self.module.params['value'] is not None:
|
||||
self.value = list(map(self.txt_helper, self.module.params['value']))
|
||||
else:
|
||||
self.value = self.module.params['value']
|
||||
|
||||
self.dns_rc = 0
|
||||
|
||||
def txt_helper(self, entry):
|
||||
if entry[0] == '"' and entry[-1] == '"':
|
||||
return entry
|
||||
return '"{text}"'.format(text=entry)
|
||||
|
||||
def lookup_zone(self):
|
||||
name = dns.name.from_text(self.module.params['record'])
|
||||
while True:
|
||||
query = dns.message.make_query(name, dns.rdatatype.SOA)
|
||||
if self.keyring:
|
||||
query.use_tsig(keyring=self.keyring, algorithm=self.algorithm)
|
||||
try:
|
||||
if self.module.params['protocol'] == 'tcp':
|
||||
lookup = dns.query.tcp(query, self.module.params['server'], timeout=10, port=self.module.params['port'])
|
||||
else:
|
||||
lookup = dns.query.udp(query, self.module.params['server'], timeout=10, port=self.module.params['port'])
|
||||
except (dns.tsig.PeerBadKey, dns.tsig.PeerBadSignature) as e:
|
||||
self.module.fail_json(msg='TSIG update error (%s): %s' % (e.__class__.__name__, to_native(e)))
|
||||
except (socket_error, dns.exception.Timeout) as e:
|
||||
self.module.fail_json(msg='DNS server error: (%s): %s' % (e.__class__.__name__, to_native(e)))
|
||||
if lookup.rcode() in [dns.rcode.SERVFAIL, dns.rcode.REFUSED]:
|
||||
self.module.fail_json(msg='Zone lookup failure: \'%s\' will not respond to queries regarding \'%s\'.' % (
|
||||
self.module.params['server'], self.module.params['record']))
|
||||
try:
|
||||
zone = lookup.authority[0].name
|
||||
if zone == name:
|
||||
return zone.to_text()
|
||||
except IndexError:
|
||||
pass
|
||||
try:
|
||||
name = name.parent()
|
||||
except dns.name.NoParent:
|
||||
self.module.fail_json(msg='Zone lookup of \'%s\' failed for unknown reason.' % (self.module.params['record']))
|
||||
|
||||
def __do_update(self, update):
|
||||
response = None
|
||||
try:
|
||||
if self.module.params['protocol'] == 'tcp':
|
||||
response = dns.query.tcp(update, self.module.params['server'], timeout=10, port=self.module.params['port'])
|
||||
else:
|
||||
response = dns.query.udp(update, self.module.params['server'], timeout=10, port=self.module.params['port'])
|
||||
except (dns.tsig.PeerBadKey, dns.tsig.PeerBadSignature) as e:
|
||||
self.module.fail_json(msg='TSIG update error (%s): %s' % (e.__class__.__name__, to_native(e)))
|
||||
except (socket_error, dns.exception.Timeout) as e:
|
||||
self.module.fail_json(msg='DNS server error: (%s): %s' % (e.__class__.__name__, to_native(e)))
|
||||
return response
|
||||
|
||||
def create_or_update_record(self):
|
||||
result = {'changed': False, 'failed': False}
|
||||
|
||||
exists = self.record_exists()
|
||||
if exists in [0, 2]:
|
||||
if self.module.check_mode:
|
||||
self.module.exit_json(changed=True)
|
||||
|
||||
if exists == 0:
|
||||
self.dns_rc = self.create_record()
|
||||
if self.dns_rc != 0:
|
||||
result['msg'] = "Failed to create DNS record (rc: %d)" % self.dns_rc
|
||||
|
||||
elif exists == 2:
|
||||
self.dns_rc = self.modify_record()
|
||||
if self.dns_rc != 0:
|
||||
result['msg'] = "Failed to update DNS record (rc: %d)" % self.dns_rc
|
||||
|
||||
if self.dns_rc != 0:
|
||||
result['failed'] = True
|
||||
else:
|
||||
result['changed'] = True
|
||||
|
||||
else:
|
||||
result['changed'] = False
|
||||
|
||||
return result
|
||||
|
||||
def create_record(self):
|
||||
update = dns.update.Update(self.zone, keyring=self.keyring, keyalgorithm=self.algorithm)
|
||||
for entry in self.value:
|
||||
try:
|
||||
update.add(self.module.params['record'],
|
||||
self.module.params['ttl'],
|
||||
self.module.params['type'],
|
||||
entry)
|
||||
except AttributeError:
|
||||
self.module.fail_json(msg='value needed when state=present')
|
||||
except dns.exception.SyntaxError:
|
||||
self.module.fail_json(msg='Invalid/malformed value')
|
||||
|
||||
response = self.__do_update(update)
|
||||
return dns.message.Message.rcode(response)
|
||||
|
||||
def modify_record(self):
|
||||
update = dns.update.Update(self.zone, keyring=self.keyring, keyalgorithm=self.algorithm)
|
||||
update.delete(self.module.params['record'], self.module.params['type'])
|
||||
for entry in self.value:
|
||||
try:
|
||||
update.add(self.module.params['record'],
|
||||
self.module.params['ttl'],
|
||||
self.module.params['type'],
|
||||
entry)
|
||||
except AttributeError:
|
||||
self.module.fail_json(msg='value needed when state=present')
|
||||
except dns.exception.SyntaxError:
|
||||
self.module.fail_json(msg='Invalid/malformed value')
|
||||
response = self.__do_update(update)
|
||||
|
||||
return dns.message.Message.rcode(response)
|
||||
|
||||
def remove_record(self):
|
||||
result = {'changed': False, 'failed': False}
|
||||
|
||||
if self.record_exists() == 0:
|
||||
return result
|
||||
|
||||
# Check mode and record exists, declared fake change.
|
||||
if self.module.check_mode:
|
||||
self.module.exit_json(changed=True)
|
||||
|
||||
update = dns.update.Update(self.zone, keyring=self.keyring, keyalgorithm=self.algorithm)
|
||||
update.delete(self.module.params['record'], self.module.params['type'])
|
||||
|
||||
response = self.__do_update(update)
|
||||
self.dns_rc = dns.message.Message.rcode(response)
|
||||
|
||||
if self.dns_rc != 0:
|
||||
result['failed'] = True
|
||||
result['msg'] = "Failed to delete record (rc: %d)" % self.dns_rc
|
||||
else:
|
||||
result['changed'] = True
|
||||
|
||||
return result
|
||||
|
||||
def record_exists(self):
|
||||
update = dns.update.Update(self.zone, keyring=self.keyring, keyalgorithm=self.algorithm)
|
||||
try:
|
||||
update.present(self.module.params['record'], self.module.params['type'])
|
||||
except dns.rdatatype.UnknownRdatatype as e:
|
||||
self.module.fail_json(msg='Record error: {0}'.format(to_native(e)))
|
||||
|
||||
response = self.__do_update(update)
|
||||
self.dns_rc = dns.message.Message.rcode(response)
|
||||
if self.dns_rc == 0:
|
||||
if self.module.params['state'] == 'absent':
|
||||
return 1
|
||||
for entry in self.value:
|
||||
try:
|
||||
update.present(self.module.params['record'], self.module.params['type'], entry)
|
||||
except AttributeError:
|
||||
self.module.fail_json(msg='value needed when state=present')
|
||||
except dns.exception.SyntaxError:
|
||||
self.module.fail_json(msg='Invalid/malformed value')
|
||||
response = self.__do_update(update)
|
||||
self.dns_rc = dns.message.Message.rcode(response)
|
||||
if self.dns_rc == 0:
|
||||
if self.ttl_changed():
|
||||
return 2
|
||||
else:
|
||||
return 1
|
||||
else:
|
||||
return 2
|
||||
else:
|
||||
return 0
|
||||
|
||||
def ttl_changed(self):
|
||||
query = dns.message.make_query(self.fqdn, self.module.params['type'])
|
||||
if self.keyring:
|
||||
query.use_tsig(keyring=self.keyring, algorithm=self.algorithm)
|
||||
|
||||
try:
|
||||
if self.module.params['protocol'] == 'tcp':
|
||||
lookup = dns.query.tcp(query, self.module.params['server'], timeout=10, port=self.module.params['port'])
|
||||
else:
|
||||
lookup = dns.query.udp(query, self.module.params['server'], timeout=10, port=self.module.params['port'])
|
||||
except (dns.tsig.PeerBadKey, dns.tsig.PeerBadSignature) as e:
|
||||
self.module.fail_json(msg='TSIG update error (%s): %s' % (e.__class__.__name__, to_native(e)))
|
||||
except (socket_error, dns.exception.Timeout) as e:
|
||||
self.module.fail_json(msg='DNS server error: (%s): %s' % (e.__class__.__name__, to_native(e)))
|
||||
|
||||
if lookup.rcode() != dns.rcode.NOERROR:
|
||||
self.module.fail_json(msg='Failed to lookup TTL of existing matching record.')
|
||||
|
||||
current_ttl = lookup.answer[0].ttl
|
||||
return current_ttl != self.module.params['ttl']
|
||||
|
||||
|
||||
def main():
|
||||
tsig_algs = ['HMAC-MD5.SIG-ALG.REG.INT', 'hmac-md5', 'hmac-sha1', 'hmac-sha224',
|
||||
'hmac-sha256', 'hmac-sha384', 'hmac-sha512']
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
state=dict(required=False, default='present', choices=['present', 'absent'], type='str'),
|
||||
server=dict(required=True, type='str'),
|
||||
port=dict(required=False, default=53, type='int'),
|
||||
key_name=dict(required=False, type='str'),
|
||||
key_secret=dict(required=False, type='str', no_log=True),
|
||||
key_algorithm=dict(required=False, default='hmac-md5', choices=tsig_algs, type='str'),
|
||||
zone=dict(required=False, default=None, type='str'),
|
||||
record=dict(required=True, type='str'),
|
||||
type=dict(required=False, default='A', type='str'),
|
||||
ttl=dict(required=False, default=3600, type='int'),
|
||||
value=dict(required=False, default=None, type='list'),
|
||||
protocol=dict(required=False, default='tcp', choices=['tcp', 'udp'], type='str')
|
||||
),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
if not HAVE_DNSPYTHON:
|
||||
module.fail_json(msg=missing_required_lib('dnspython'), exception=DNSPYTHON_IMP_ERR)
|
||||
|
||||
if len(module.params["record"]) == 0:
|
||||
module.fail_json(msg='record cannot be empty.')
|
||||
|
||||
record = RecordManager(module)
|
||||
result = {}
|
||||
if module.params["state"] == 'absent':
|
||||
result = record.remove_record()
|
||||
elif module.params["state"] == 'present':
|
||||
result = record.create_or_update_record()
|
||||
|
||||
result['dns_rc'] = record.dns_rc
|
||||
result['dns_rc_str'] = dns.rcode.to_text(record.dns_rc)
|
||||
if result['failed']:
|
||||
module.fail_json(**result)
|
||||
else:
|
||||
result['record'] = dict(zone=record.zone,
|
||||
record=module.params['record'],
|
||||
type=module.params['type'],
|
||||
ttl=module.params['ttl'],
|
||||
value=record.value)
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
314
plugins/modules/net_tools/omapi_host.py
Normal file
314
plugins/modules/net_tools/omapi_host.py
Normal file
@@ -0,0 +1,314 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# copyright: (c) 2016, Loic Blot <loic.blot@unix-experience.fr>
|
||||
# Sponsored by Infopro Digital. http://www.infopro-digital.com/
|
||||
# Sponsored by E.T.A.I. http://www.etai.fr/
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: omapi_host
|
||||
short_description: Setup OMAPI hosts.
|
||||
description: Manage OMAPI hosts into compatible DHCPd servers
|
||||
requirements:
|
||||
- pypureomapi
|
||||
author:
|
||||
- Loic Blot (@nerzhul)
|
||||
options:
|
||||
state:
|
||||
description:
|
||||
- Create or remove OMAPI host.
|
||||
type: str
|
||||
required: true
|
||||
choices: [ absent, present ]
|
||||
hostname:
|
||||
description:
|
||||
- Sets the host lease hostname (mandatory if state=present).
|
||||
type: str
|
||||
aliases: [ name ]
|
||||
host:
|
||||
description:
|
||||
- Sets OMAPI server host to interact with.
|
||||
type: str
|
||||
default: localhost
|
||||
port:
|
||||
description:
|
||||
- Sets the OMAPI server port to interact with.
|
||||
type: int
|
||||
default: 7911
|
||||
key_name:
|
||||
description:
|
||||
- Sets the TSIG key name for authenticating against OMAPI server.
|
||||
type: str
|
||||
required: true
|
||||
key:
|
||||
description:
|
||||
- Sets the TSIG key content for authenticating against OMAPI server.
|
||||
type: str
|
||||
required: true
|
||||
macaddr:
|
||||
description:
|
||||
- Sets the lease host MAC address.
|
||||
type: str
|
||||
required: true
|
||||
ip:
|
||||
description:
|
||||
- Sets the lease host IP address.
|
||||
type: str
|
||||
statements:
|
||||
description:
|
||||
- Attach a list of OMAPI DHCP statements with host lease (without ending semicolon).
|
||||
type: list
|
||||
default: []
|
||||
ddns:
|
||||
description:
|
||||
- Enable dynamic DNS updates for this host.
|
||||
type: bool
|
||||
default: no
|
||||
|
||||
'''
|
||||
EXAMPLES = r'''
|
||||
- name: Add a host using OMAPI
|
||||
omapi_host:
|
||||
key_name: defomapi
|
||||
key: +bFQtBCta6j2vWkjPkNFtgA==
|
||||
host: 10.98.4.55
|
||||
macaddr: 44:dd:ab:dd:11:44
|
||||
name: server01
|
||||
ip: 192.168.88.99
|
||||
ddns: yes
|
||||
statements:
|
||||
- filename "pxelinux.0"
|
||||
- next-server 1.1.1.1
|
||||
state: present
|
||||
|
||||
- name: Remove a host using OMAPI
|
||||
omapi_host:
|
||||
key_name: defomapi
|
||||
key: +bFQtBCta6j2vWkjPkNFtgA==
|
||||
host: 10.1.1.1
|
||||
macaddr: 00:66:ab:dd:11:44
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
lease:
|
||||
description: dictionary containing host information
|
||||
returned: success
|
||||
type: complex
|
||||
contains:
|
||||
ip-address:
|
||||
description: IP address, if there is.
|
||||
returned: success
|
||||
type: str
|
||||
sample: '192.168.1.5'
|
||||
hardware-address:
|
||||
description: MAC address
|
||||
returned: success
|
||||
type: str
|
||||
sample: '00:11:22:33:44:55'
|
||||
hardware-type:
|
||||
description: hardware type, generally '1'
|
||||
returned: success
|
||||
type: int
|
||||
sample: 1
|
||||
name:
|
||||
description: hostname
|
||||
returned: success
|
||||
type: str
|
||||
sample: 'mydesktop'
|
||||
'''
|
||||
|
||||
import binascii
|
||||
import socket
|
||||
import struct
|
||||
import traceback
|
||||
|
||||
PUREOMAPI_IMP_ERR = None
|
||||
try:
|
||||
from pypureomapi import Omapi, OmapiMessage, OmapiError, OmapiErrorNotFound
|
||||
from pypureomapi import pack_ip, unpack_ip, pack_mac, unpack_mac
|
||||
from pypureomapi import OMAPI_OP_STATUS, OMAPI_OP_UPDATE
|
||||
pureomapi_found = True
|
||||
except ImportError:
|
||||
PUREOMAPI_IMP_ERR = traceback.format_exc()
|
||||
pureomapi_found = False
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
from ansible.module_utils._text import to_bytes, to_native
|
||||
|
||||
|
||||
class OmapiHostManager:
|
||||
def __init__(self, module):
|
||||
self.module = module
|
||||
self.omapi = None
|
||||
self.connect()
|
||||
|
||||
def connect(self):
|
||||
try:
|
||||
self.omapi = Omapi(self.module.params['host'], self.module.params['port'], self.module.params['key_name'],
|
||||
self.module.params['key'])
|
||||
except binascii.Error:
|
||||
self.module.fail_json(msg="Unable to open OMAPI connection. 'key' is not a valid base64 key.")
|
||||
except OmapiError as e:
|
||||
self.module.fail_json(msg="Unable to open OMAPI connection. Ensure 'host', 'port', 'key' and 'key_name' "
|
||||
"are valid. Exception was: %s" % to_native(e))
|
||||
except socket.error as e:
|
||||
self.module.fail_json(msg="Unable to connect to OMAPI server: %s" % to_native(e))
|
||||
|
||||
def get_host(self, macaddr):
|
||||
msg = OmapiMessage.open(to_bytes("host", errors='surrogate_or_strict'))
|
||||
msg.obj.append((to_bytes("hardware-address", errors='surrogate_or_strict'), pack_mac(macaddr)))
|
||||
msg.obj.append((to_bytes("hardware-type", errors='surrogate_or_strict'), struct.pack("!I", 1)))
|
||||
response = self.omapi.query_server(msg)
|
||||
if response.opcode != OMAPI_OP_UPDATE:
|
||||
return None
|
||||
return response
|
||||
|
||||
@staticmethod
|
||||
def unpack_facts(obj):
|
||||
result = dict(obj)
|
||||
if 'hardware-address' in result:
|
||||
result['hardware-address'] = unpack_mac(result['hardware-address'])
|
||||
|
||||
if 'ip-address' in result:
|
||||
result['ip-address'] = unpack_ip(result['ip-address'])
|
||||
|
||||
if 'hardware-type' in result:
|
||||
result['hardware-type'] = struct.unpack("!I", result['hardware-type'])
|
||||
|
||||
return result
|
||||
|
||||
def setup_host(self):
|
||||
if self.module.params['hostname'] is None or len(self.module.params['hostname']) == 0:
|
||||
self.module.fail_json(msg="name attribute could not be empty when adding or modifying host.")
|
||||
|
||||
msg = None
|
||||
host_response = self.get_host(self.module.params['macaddr'])
|
||||
# If host was not found using macaddr, add create message
|
||||
if host_response is None:
|
||||
msg = OmapiMessage.open(to_bytes('host', errors='surrogate_or_strict'))
|
||||
msg.message.append(('create', struct.pack('!I', 1)))
|
||||
msg.message.append(('exclusive', struct.pack('!I', 1)))
|
||||
msg.obj.append(('hardware-address', pack_mac(self.module.params['macaddr'])))
|
||||
msg.obj.append(('hardware-type', struct.pack('!I', 1)))
|
||||
msg.obj.append(('name', self.module.params['hostname']))
|
||||
if self.module.params['ip'] is not None:
|
||||
msg.obj.append((to_bytes("ip-address", errors='surrogate_or_strict'), pack_ip(self.module.params['ip'])))
|
||||
|
||||
stmt_join = ""
|
||||
if self.module.params['ddns']:
|
||||
stmt_join += 'ddns-hostname "{0}"; '.format(self.module.params['hostname'])
|
||||
|
||||
try:
|
||||
if len(self.module.params['statements']) > 0:
|
||||
stmt_join += "; ".join(self.module.params['statements'])
|
||||
stmt_join += "; "
|
||||
except TypeError as e:
|
||||
self.module.fail_json(msg="Invalid statements found: %s" % to_native(e))
|
||||
|
||||
if len(stmt_join) > 0:
|
||||
msg.obj.append(('statements', stmt_join))
|
||||
|
||||
try:
|
||||
response = self.omapi.query_server(msg)
|
||||
if response.opcode != OMAPI_OP_UPDATE:
|
||||
self.module.fail_json(msg="Failed to add host, ensure authentication and host parameters "
|
||||
"are valid.")
|
||||
self.module.exit_json(changed=True, lease=self.unpack_facts(response.obj))
|
||||
except OmapiError as e:
|
||||
self.module.fail_json(msg="OMAPI error: %s" % to_native(e))
|
||||
# Forge update message
|
||||
else:
|
||||
response_obj = self.unpack_facts(host_response.obj)
|
||||
fields_to_update = {}
|
||||
|
||||
if to_bytes('ip-address', errors='surrogate_or_strict') not in response_obj or \
|
||||
unpack_ip(response_obj[to_bytes('ip-address', errors='surrogate_or_strict')]) != self.module.params['ip']:
|
||||
fields_to_update['ip-address'] = pack_ip(self.module.params['ip'])
|
||||
|
||||
# Name cannot be changed
|
||||
if 'name' not in response_obj or response_obj['name'] != self.module.params['hostname']:
|
||||
self.module.fail_json(msg="Changing hostname is not supported. Old was %s, new is %s. "
|
||||
"Please delete host and add new." %
|
||||
(response_obj['name'], self.module.params['hostname']))
|
||||
|
||||
"""
|
||||
# It seems statements are not returned by OMAPI, then we cannot modify them at this moment.
|
||||
if 'statements' not in response_obj and len(self.module.params['statements']) > 0 or \
|
||||
response_obj['statements'] != self.module.params['statements']:
|
||||
with open('/tmp/omapi', 'w') as fb:
|
||||
for (k,v) in iteritems(response_obj):
|
||||
fb.writelines('statements: %s %s\n' % (k, v))
|
||||
"""
|
||||
if len(fields_to_update) == 0:
|
||||
self.module.exit_json(changed=False, lease=response_obj)
|
||||
else:
|
||||
msg = OmapiMessage.update(host_response.handle)
|
||||
msg.update_object(fields_to_update)
|
||||
|
||||
try:
|
||||
response = self.omapi.query_server(msg)
|
||||
if response.opcode != OMAPI_OP_STATUS:
|
||||
self.module.fail_json(msg="Failed to modify host, ensure authentication and host parameters "
|
||||
"are valid.")
|
||||
self.module.exit_json(changed=True)
|
||||
except OmapiError as e:
|
||||
self.module.fail_json(msg="OMAPI error: %s" % to_native(e))
|
||||
|
||||
def remove_host(self):
|
||||
try:
|
||||
self.omapi.del_host(self.module.params['macaddr'])
|
||||
self.module.exit_json(changed=True)
|
||||
except OmapiErrorNotFound:
|
||||
self.module.exit_json()
|
||||
except OmapiError as e:
|
||||
self.module.fail_json(msg="OMAPI error: %s" % to_native(e))
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
state=dict(type='str', required=True, choices=['absent', 'present']),
|
||||
host=dict(type='str', default="localhost"),
|
||||
port=dict(type='int', default=7911),
|
||||
key_name=dict(type='str', required=True),
|
||||
key=dict(type='str', required=True, no_log=True),
|
||||
macaddr=dict(type='str', required=True),
|
||||
hostname=dict(type='str', aliases=['name']),
|
||||
ip=dict(type='str'),
|
||||
ddns=dict(type='bool', default=False),
|
||||
statements=dict(type='list', default=[]),
|
||||
),
|
||||
supports_check_mode=False,
|
||||
)
|
||||
|
||||
if not pureomapi_found:
|
||||
module.fail_json(msg=missing_required_lib('pypureomapi'), exception=PUREOMAPI_IMP_ERR)
|
||||
|
||||
if module.params['key'] is None or len(module.params["key"]) == 0:
|
||||
module.fail_json(msg="'key' parameter cannot be empty.")
|
||||
|
||||
if module.params['key_name'] is None or len(module.params["key_name"]) == 0:
|
||||
module.fail_json(msg="'key_name' parameter cannot be empty.")
|
||||
|
||||
host_manager = OmapiHostManager(module)
|
||||
try:
|
||||
if module.params['state'] == 'present':
|
||||
host_manager.setup_host()
|
||||
elif module.params['state'] == 'absent':
|
||||
host_manager.remove_host()
|
||||
except ValueError as e:
|
||||
module.fail_json(msg="OMAPI input value error: %s" % to_native(e))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
464
plugins/modules/net_tools/snmp_facts.py
Normal file
464
plugins/modules/net_tools/snmp_facts.py
Normal file
@@ -0,0 +1,464 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# This file is part of Networklore's snmp library for Ansible
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: snmp_facts
|
||||
author:
|
||||
- Patrick Ogenstad (@ogenstad)
|
||||
short_description: Retrieve facts for a device using SNMP
|
||||
description:
|
||||
- Retrieve facts for a device using SNMP, the facts will be
|
||||
inserted to the ansible_facts key.
|
||||
requirements:
|
||||
- pysnmp
|
||||
options:
|
||||
host:
|
||||
description:
|
||||
- Set to target snmp server (normally C({{ inventory_hostname }})).
|
||||
type: str
|
||||
required: true
|
||||
version:
|
||||
description:
|
||||
- SNMP Version to use, v2/v2c or v3.
|
||||
type: str
|
||||
required: true
|
||||
choices: [ v2, v2c, v3 ]
|
||||
community:
|
||||
description:
|
||||
- The SNMP community string, required if version is v2/v2c.
|
||||
type: str
|
||||
level:
|
||||
description:
|
||||
- Authentication level.
|
||||
- Required if version is v3.
|
||||
type: str
|
||||
choices: [ authNoPriv, authPriv ]
|
||||
username:
|
||||
description:
|
||||
- Username for SNMPv3.
|
||||
- Required if version is v3.
|
||||
type: str
|
||||
integrity:
|
||||
description:
|
||||
- Hashing algorithm.
|
||||
- Required if version is v3.
|
||||
type: str
|
||||
choices: [ md5, sha ]
|
||||
authkey:
|
||||
description:
|
||||
- Authentication key.
|
||||
- Required if version is v3.
|
||||
type: str
|
||||
privacy:
|
||||
description:
|
||||
- Encryption algorithm.
|
||||
- Required if level is authPriv.
|
||||
type: str
|
||||
choices: [ aes, des ]
|
||||
privkey:
|
||||
description:
|
||||
- Encryption key.
|
||||
- Required if version is authPriv.
|
||||
type: str
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Gather facts with SNMP version 2
|
||||
snmp_facts:
|
||||
host: '{{ inventory_hostname }}'
|
||||
version: v2c
|
||||
community: public
|
||||
delegate_to: local
|
||||
|
||||
- name: Gather facts using SNMP version 3
|
||||
snmp_facts:
|
||||
host: '{{ inventory_hostname }}'
|
||||
version: v3
|
||||
level: authPriv
|
||||
integrity: sha
|
||||
privacy: aes
|
||||
username: snmp-user
|
||||
authkey: abc12345
|
||||
privkey: def6789
|
||||
delegate_to: localhost
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
ansible_sysdescr:
|
||||
description: A textual description of the entity.
|
||||
returned: success
|
||||
type: str
|
||||
sample: Linux ubuntu-user 4.4.0-93-generic #116-Ubuntu SMP Fri Aug 11 21:17:51 UTC 2017 x86_64
|
||||
ansible_sysobjectid:
|
||||
description: The vendor's authoritative identification of the network management subsystem contained in the entity.
|
||||
returned: success
|
||||
type: str
|
||||
sample: 1.3.6.1.4.1.8072.3.2.10
|
||||
ansible_sysuptime:
|
||||
description: The time (in hundredths of a second) since the network management portion of the system was last re-initialized.
|
||||
returned: success
|
||||
type: int
|
||||
sample: 42388
|
||||
ansible_syscontact:
|
||||
description: The textual identification of the contact person for this managed node, together with information on how to contact this person.
|
||||
returned: success
|
||||
type: str
|
||||
sample: Me <me@example.org>
|
||||
ansible_sysname:
|
||||
description: An administratively-assigned name for this managed node.
|
||||
returned: success
|
||||
type: str
|
||||
sample: ubuntu-user
|
||||
ansible_syslocation:
|
||||
description: The physical location of this node (e.g., `telephone closet, 3rd floor').
|
||||
returned: success
|
||||
type: str
|
||||
sample: Sitting on the Dock of the Bay
|
||||
ansible_all_ipv4_addresses:
|
||||
description: List of all IPv4 addresses.
|
||||
returned: success
|
||||
type: list
|
||||
sample: ["127.0.0.1", "172.17.0.1"]
|
||||
ansible_interfaces:
|
||||
description: Dictionary of each network interface and its metadata.
|
||||
returned: success
|
||||
type: dict
|
||||
sample: {
|
||||
"1": {
|
||||
"adminstatus": "up",
|
||||
"description": "",
|
||||
"ifindex": "1",
|
||||
"ipv4": [
|
||||
{
|
||||
"address": "127.0.0.1",
|
||||
"netmask": "255.0.0.0"
|
||||
}
|
||||
],
|
||||
"mac": "",
|
||||
"mtu": "65536",
|
||||
"name": "lo",
|
||||
"operstatus": "up",
|
||||
"speed": "65536"
|
||||
},
|
||||
"2": {
|
||||
"adminstatus": "up",
|
||||
"description": "",
|
||||
"ifindex": "2",
|
||||
"ipv4": [
|
||||
{
|
||||
"address": "192.168.213.128",
|
||||
"netmask": "255.255.255.0"
|
||||
}
|
||||
],
|
||||
"mac": "000a305a52a1",
|
||||
"mtu": "1500",
|
||||
"name": "Intel Corporation 82545EM Gigabit Ethernet Controller (Copper)",
|
||||
"operstatus": "up",
|
||||
"speed": "1500"
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
import binascii
|
||||
import traceback
|
||||
from collections import defaultdict
|
||||
|
||||
PYSNMP_IMP_ERR = None
|
||||
try:
|
||||
from pysnmp.entity.rfc3413.oneliner import cmdgen
|
||||
has_pysnmp = True
|
||||
except Exception:
|
||||
PYSNMP_IMP_ERR = traceback.format_exc()
|
||||
has_pysnmp = False
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
from ansible.module_utils._text import to_text
|
||||
|
||||
|
||||
class DefineOid(object):
|
||||
|
||||
def __init__(self, dotprefix=False):
|
||||
if dotprefix:
|
||||
dp = "."
|
||||
else:
|
||||
dp = ""
|
||||
|
||||
# From SNMPv2-MIB
|
||||
self.sysDescr = dp + "1.3.6.1.2.1.1.1.0"
|
||||
self.sysObjectId = dp + "1.3.6.1.2.1.1.2.0"
|
||||
self.sysUpTime = dp + "1.3.6.1.2.1.1.3.0"
|
||||
self.sysContact = dp + "1.3.6.1.2.1.1.4.0"
|
||||
self.sysName = dp + "1.3.6.1.2.1.1.5.0"
|
||||
self.sysLocation = dp + "1.3.6.1.2.1.1.6.0"
|
||||
|
||||
# From IF-MIB
|
||||
self.ifIndex = dp + "1.3.6.1.2.1.2.2.1.1"
|
||||
self.ifDescr = dp + "1.3.6.1.2.1.2.2.1.2"
|
||||
self.ifMtu = dp + "1.3.6.1.2.1.2.2.1.4"
|
||||
self.ifSpeed = dp + "1.3.6.1.2.1.2.2.1.5"
|
||||
self.ifPhysAddress = dp + "1.3.6.1.2.1.2.2.1.6"
|
||||
self.ifAdminStatus = dp + "1.3.6.1.2.1.2.2.1.7"
|
||||
self.ifOperStatus = dp + "1.3.6.1.2.1.2.2.1.8"
|
||||
self.ifAlias = dp + "1.3.6.1.2.1.31.1.1.1.18"
|
||||
|
||||
# From IP-MIB
|
||||
self.ipAdEntAddr = dp + "1.3.6.1.2.1.4.20.1.1"
|
||||
self.ipAdEntIfIndex = dp + "1.3.6.1.2.1.4.20.1.2"
|
||||
self.ipAdEntNetMask = dp + "1.3.6.1.2.1.4.20.1.3"
|
||||
|
||||
|
||||
def decode_hex(hexstring):
|
||||
|
||||
if len(hexstring) < 3:
|
||||
return hexstring
|
||||
if hexstring[:2] == "0x":
|
||||
return to_text(binascii.unhexlify(hexstring[2:]))
|
||||
else:
|
||||
return hexstring
|
||||
|
||||
|
||||
def decode_mac(hexstring):
|
||||
|
||||
if len(hexstring) != 14:
|
||||
return hexstring
|
||||
if hexstring[:2] == "0x":
|
||||
return hexstring[2:]
|
||||
else:
|
||||
return hexstring
|
||||
|
||||
|
||||
def lookup_adminstatus(int_adminstatus):
|
||||
adminstatus_options = {
|
||||
1: 'up',
|
||||
2: 'down',
|
||||
3: 'testing'
|
||||
}
|
||||
if int_adminstatus in adminstatus_options:
|
||||
return adminstatus_options[int_adminstatus]
|
||||
else:
|
||||
return ""
|
||||
|
||||
|
||||
def lookup_operstatus(int_operstatus):
|
||||
operstatus_options = {
|
||||
1: 'up',
|
||||
2: 'down',
|
||||
3: 'testing',
|
||||
4: 'unknown',
|
||||
5: 'dormant',
|
||||
6: 'notPresent',
|
||||
7: 'lowerLayerDown'
|
||||
}
|
||||
if int_operstatus in operstatus_options:
|
||||
return operstatus_options[int_operstatus]
|
||||
else:
|
||||
return ""
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
host=dict(type='str', required=True),
|
||||
version=dict(type='str', required=True, choices=['v2', 'v2c', 'v3']),
|
||||
community=dict(type='str'),
|
||||
username=dict(type='str'),
|
||||
level=dict(type='str', choices=['authNoPriv', 'authPriv']),
|
||||
integrity=dict(type='str', choices=['md5', 'sha']),
|
||||
privacy=dict(type='str', choices=['aes', 'des']),
|
||||
authkey=dict(type='str'),
|
||||
privkey=dict(type='str'),
|
||||
),
|
||||
required_together=(
|
||||
['username', 'level', 'integrity', 'authkey'],
|
||||
['privacy', 'privkey'],
|
||||
),
|
||||
supports_check_mode=False,
|
||||
)
|
||||
|
||||
m_args = module.params
|
||||
|
||||
if not has_pysnmp:
|
||||
module.fail_json(msg=missing_required_lib('pysnmp'), exception=PYSNMP_IMP_ERR)
|
||||
|
||||
cmdGen = cmdgen.CommandGenerator()
|
||||
|
||||
# Verify that we receive a community when using snmp v2
|
||||
if m_args['version'] == "v2" or m_args['version'] == "v2c":
|
||||
if m_args['community'] is None:
|
||||
module.fail_json(msg='Community not set when using snmp version 2')
|
||||
|
||||
if m_args['version'] == "v3":
|
||||
if m_args['username'] is None:
|
||||
module.fail_json(msg='Username not set when using snmp version 3')
|
||||
|
||||
if m_args['level'] == "authPriv" and m_args['privacy'] is None:
|
||||
module.fail_json(msg='Privacy algorithm not set when using authPriv')
|
||||
|
||||
if m_args['integrity'] == "sha":
|
||||
integrity_proto = cmdgen.usmHMACSHAAuthProtocol
|
||||
elif m_args['integrity'] == "md5":
|
||||
integrity_proto = cmdgen.usmHMACMD5AuthProtocol
|
||||
|
||||
if m_args['privacy'] == "aes":
|
||||
privacy_proto = cmdgen.usmAesCfb128Protocol
|
||||
elif m_args['privacy'] == "des":
|
||||
privacy_proto = cmdgen.usmDESPrivProtocol
|
||||
|
||||
# Use SNMP Version 2
|
||||
if m_args['version'] == "v2" or m_args['version'] == "v2c":
|
||||
snmp_auth = cmdgen.CommunityData(m_args['community'])
|
||||
|
||||
# Use SNMP Version 3 with authNoPriv
|
||||
elif m_args['level'] == "authNoPriv":
|
||||
snmp_auth = cmdgen.UsmUserData(m_args['username'], authKey=m_args['authkey'], authProtocol=integrity_proto)
|
||||
|
||||
# Use SNMP Version 3 with authPriv
|
||||
else:
|
||||
snmp_auth = cmdgen.UsmUserData(m_args['username'], authKey=m_args['authkey'], privKey=m_args['privkey'], authProtocol=integrity_proto,
|
||||
privProtocol=privacy_proto)
|
||||
|
||||
# Use p to prefix OIDs with a dot for polling
|
||||
p = DefineOid(dotprefix=True)
|
||||
# Use v without a prefix to use with return values
|
||||
v = DefineOid(dotprefix=False)
|
||||
|
||||
def Tree():
|
||||
return defaultdict(Tree)
|
||||
|
||||
results = Tree()
|
||||
|
||||
errorIndication, errorStatus, errorIndex, varBinds = cmdGen.getCmd(
|
||||
snmp_auth,
|
||||
cmdgen.UdpTransportTarget((m_args['host'], 161)),
|
||||
cmdgen.MibVariable(p.sysDescr,),
|
||||
cmdgen.MibVariable(p.sysObjectId,),
|
||||
cmdgen.MibVariable(p.sysUpTime,),
|
||||
cmdgen.MibVariable(p.sysContact,),
|
||||
cmdgen.MibVariable(p.sysName,),
|
||||
cmdgen.MibVariable(p.sysLocation,),
|
||||
lookupMib=False
|
||||
)
|
||||
|
||||
if errorIndication:
|
||||
module.fail_json(msg=str(errorIndication))
|
||||
|
||||
for oid, val in varBinds:
|
||||
current_oid = oid.prettyPrint()
|
||||
current_val = val.prettyPrint()
|
||||
if current_oid == v.sysDescr:
|
||||
results['ansible_sysdescr'] = decode_hex(current_val)
|
||||
elif current_oid == v.sysObjectId:
|
||||
results['ansible_sysobjectid'] = current_val
|
||||
elif current_oid == v.sysUpTime:
|
||||
results['ansible_sysuptime'] = current_val
|
||||
elif current_oid == v.sysContact:
|
||||
results['ansible_syscontact'] = current_val
|
||||
elif current_oid == v.sysName:
|
||||
results['ansible_sysname'] = current_val
|
||||
elif current_oid == v.sysLocation:
|
||||
results['ansible_syslocation'] = current_val
|
||||
|
||||
errorIndication, errorStatus, errorIndex, varTable = cmdGen.nextCmd(
|
||||
snmp_auth,
|
||||
cmdgen.UdpTransportTarget((m_args['host'], 161)),
|
||||
cmdgen.MibVariable(p.ifIndex,),
|
||||
cmdgen.MibVariable(p.ifDescr,),
|
||||
cmdgen.MibVariable(p.ifMtu,),
|
||||
cmdgen.MibVariable(p.ifSpeed,),
|
||||
cmdgen.MibVariable(p.ifPhysAddress,),
|
||||
cmdgen.MibVariable(p.ifAdminStatus,),
|
||||
cmdgen.MibVariable(p.ifOperStatus,),
|
||||
cmdgen.MibVariable(p.ipAdEntAddr,),
|
||||
cmdgen.MibVariable(p.ipAdEntIfIndex,),
|
||||
cmdgen.MibVariable(p.ipAdEntNetMask,),
|
||||
|
||||
cmdgen.MibVariable(p.ifAlias,),
|
||||
lookupMib=False
|
||||
)
|
||||
|
||||
if errorIndication:
|
||||
module.fail_json(msg=str(errorIndication))
|
||||
|
||||
interface_indexes = []
|
||||
|
||||
all_ipv4_addresses = []
|
||||
ipv4_networks = Tree()
|
||||
|
||||
for varBinds in varTable:
|
||||
for oid, val in varBinds:
|
||||
current_oid = oid.prettyPrint()
|
||||
current_val = val.prettyPrint()
|
||||
if v.ifIndex in current_oid:
|
||||
ifIndex = int(current_oid.rsplit('.', 1)[-1])
|
||||
results['ansible_interfaces'][ifIndex]['ifindex'] = current_val
|
||||
interface_indexes.append(ifIndex)
|
||||
if v.ifDescr in current_oid:
|
||||
ifIndex = int(current_oid.rsplit('.', 1)[-1])
|
||||
results['ansible_interfaces'][ifIndex]['name'] = current_val
|
||||
if v.ifMtu in current_oid:
|
||||
ifIndex = int(current_oid.rsplit('.', 1)[-1])
|
||||
results['ansible_interfaces'][ifIndex]['mtu'] = current_val
|
||||
if v.ifSpeed in current_oid:
|
||||
ifIndex = int(current_oid.rsplit('.', 1)[-1])
|
||||
results['ansible_interfaces'][ifIndex]['speed'] = current_val
|
||||
if v.ifPhysAddress in current_oid:
|
||||
ifIndex = int(current_oid.rsplit('.', 1)[-1])
|
||||
results['ansible_interfaces'][ifIndex]['mac'] = decode_mac(current_val)
|
||||
if v.ifAdminStatus in current_oid:
|
||||
ifIndex = int(current_oid.rsplit('.', 1)[-1])
|
||||
results['ansible_interfaces'][ifIndex]['adminstatus'] = lookup_adminstatus(int(current_val))
|
||||
if v.ifOperStatus in current_oid:
|
||||
ifIndex = int(current_oid.rsplit('.', 1)[-1])
|
||||
results['ansible_interfaces'][ifIndex]['operstatus'] = lookup_operstatus(int(current_val))
|
||||
if v.ipAdEntAddr in current_oid:
|
||||
curIPList = current_oid.rsplit('.', 4)[-4:]
|
||||
curIP = ".".join(curIPList)
|
||||
ipv4_networks[curIP]['address'] = current_val
|
||||
all_ipv4_addresses.append(current_val)
|
||||
if v.ipAdEntIfIndex in current_oid:
|
||||
curIPList = current_oid.rsplit('.', 4)[-4:]
|
||||
curIP = ".".join(curIPList)
|
||||
ipv4_networks[curIP]['interface'] = current_val
|
||||
if v.ipAdEntNetMask in current_oid:
|
||||
curIPList = current_oid.rsplit('.', 4)[-4:]
|
||||
curIP = ".".join(curIPList)
|
||||
ipv4_networks[curIP]['netmask'] = current_val
|
||||
|
||||
if v.ifAlias in current_oid:
|
||||
ifIndex = int(current_oid.rsplit('.', 1)[-1])
|
||||
results['ansible_interfaces'][ifIndex]['description'] = current_val
|
||||
|
||||
interface_to_ipv4 = {}
|
||||
for ipv4_network in ipv4_networks:
|
||||
current_interface = ipv4_networks[ipv4_network]['interface']
|
||||
current_network = {
|
||||
'address': ipv4_networks[ipv4_network]['address'],
|
||||
'netmask': ipv4_networks[ipv4_network]['netmask']
|
||||
}
|
||||
if current_interface not in interface_to_ipv4:
|
||||
interface_to_ipv4[current_interface] = []
|
||||
interface_to_ipv4[current_interface].append(current_network)
|
||||
else:
|
||||
interface_to_ipv4[current_interface].append(current_network)
|
||||
|
||||
for interface in interface_to_ipv4:
|
||||
results['ansible_interfaces'][int(interface)]['ipv4'] = interface_to_ipv4[interface]
|
||||
|
||||
results['ansible_all_ipv4_addresses'] = all_ipv4_addresses
|
||||
|
||||
module.exit_json(ansible_facts=results)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user