Initial commit

This commit is contained in:
Ansible Core Team 2020-03-09 09:11:07 +00:00
commit aebc1b03fd
4861 changed files with 812621 additions and 0 deletions

View file

@ -0,0 +1,188 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2018, Simon Weald <ansible@simonweald.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: memset_dns_reload
author: "Simon Weald (@glitchcrab)"
short_description: Request reload of Memset's DNS infrastructure,
notes:
- DNS reload requests are a best-effort service provided by Memset; these generally
happen every 15 minutes by default, however you can request an immediate reload if
later tasks rely on the records being created. An API key generated via the
Memset customer control panel is required with the following minimum scope -
I(dns.reload). If you wish to poll the job status to wait until the reload has
completed, then I(job.status) is also required.
description:
- Request a reload of Memset's DNS infrastructure, and optionally poll until it finishes.
options:
api_key:
required: true
description:
- The API key obtained from the Memset control panel.
poll:
default: false
type: bool
description:
- Boolean value, if set will poll the reload job's status and return
when the job has completed (unless the 30 second timeout is reached first).
If the timeout is reached then the task will not be marked as failed, but
stderr will indicate that the polling failed.
'''
EXAMPLES = '''
- name: submit DNS reload and poll.
memset_dns_reload:
api_key: 5eb86c9196ab03919abcf03857163741
poll: True
delegate_to: localhost
'''
RETURN = '''
---
memset_api:
description: Raw response from the Memset API.
returned: always
type: complex
contains:
error:
description: Whether the job ended in error state.
returned: always
type: bool
sample: true
finished:
description: Whether the job completed before the result was returned.
returned: always
type: bool
sample: true
id:
description: Job ID.
returned: always
type: str
sample: "c9cc8ad2a3e3fb8c63ed83c424928ef8"
status:
description: Job status.
returned: always
type: str
sample: "DONE"
type:
description: Job type.
returned: always
type: str
sample: "dns"
'''
from time import sleep
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.memset import memset_api_call
def poll_reload_status(api_key=None, job_id=None, payload=None):
'''
We poll the `job.status` endpoint every 5 seconds up to a
maximum of 6 times. This is a relatively arbitrary choice of
timeout, however requests rarely take longer than 15 seconds
to complete.
'''
memset_api, stderr, msg = None, None, None
payload['id'] = job_id
api_method = 'job.status'
_has_failed, _msg, response = memset_api_call(api_key=api_key, api_method=api_method, payload=payload)
while not response.json()['finished']:
counter = 0
while counter < 6:
sleep(5)
_has_failed, msg, response = memset_api_call(api_key=api_key, api_method=api_method, payload=payload)
counter += 1
if response.json()['error']:
# the reload job was submitted but polling failed. Don't return this as an overall task failure.
stderr = "Reload submitted successfully, but the Memset API returned a job error when attempting to poll the reload status."
else:
memset_api = response.json()
msg = None
return(memset_api, msg, stderr)
def reload_dns(args=None):
'''
DNS reloads are a single API call and therefore there's not much
which can go wrong outside of auth errors.
'''
retvals, payload = dict(), dict()
has_changed, has_failed = False, False
memset_api, msg, stderr = None, None, None
api_method = 'dns.reload'
has_failed, msg, response = memset_api_call(api_key=args['api_key'], api_method=api_method)
if has_failed:
# this is the first time the API is called; incorrect credentials will
# manifest themselves at this point so we need to ensure the user is
# informed of the reason.
retvals['failed'] = has_failed
retvals['memset_api'] = response.json()
retvals['msg'] = msg
return(retvals)
# set changed to true if the reload request was accepted.
has_changed = True
memset_api = msg
# empty msg var as we don't want to return the API's json response twice.
msg = None
if args['poll']:
# hand off to the poll function.
job_id = response.json()['id']
memset_api, msg, stderr = poll_reload_status(api_key=args['api_key'], job_id=job_id, payload=payload)
# assemble return variables.
retvals['failed'] = has_failed
retvals['changed'] = has_changed
for val in ['msg', 'stderr', 'memset_api']:
if val is not None:
retvals[val] = eval(val)
return(retvals)
def main():
global module
module = AnsibleModule(
argument_spec=dict(
api_key=dict(required=True, type='str', no_log=True),
poll=dict(required=False, default=False, type='bool')
),
supports_check_mode=False
)
# populate the dict with the user-provided vars.
args = dict()
for key, arg in module.params.items():
args[key] = arg
retvals = reload_dns(args)
if retvals['failed']:
module.fail_json(**retvals)
else:
module.exit_json(**retvals)
if __name__ == '__main__':
main()

View file

@ -0,0 +1 @@
memset_memstore_info.py

View file

@ -0,0 +1,175 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2018, Simon Weald <ansible@simonweald.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: memset_memstore_info
author: "Simon Weald (@glitchcrab)"
short_description: Retrieve Memstore product usage information.
notes:
- An API key generated via the Memset customer control panel is needed with the
following minimum scope - I(memstore.usage).
description:
- Retrieve Memstore product usage information.
- This module was called C(memset_memstore_facts) before Ansible 2.9. The usage did not change.
options:
api_key:
required: true
description:
- The API key obtained from the Memset control panel.
name:
required: true
description:
- The Memstore product name (i.e. C(mstestyaa1)).
'''
EXAMPLES = '''
- name: get usage for mstestyaa1
memset_memstore_info:
name: mstestyaa1
api_key: 5eb86c9896ab03919abcf03857163741
delegate_to: localhost
'''
RETURN = '''
---
memset_api:
description: Info from the Memset API
returned: always
type: complex
contains:
cdn_bandwidth:
description: Dictionary of CDN bandwidth facts
returned: always
type: complex
contains:
bytes_out:
description: Outbound CDN bandwidth for the last 24 hours in bytes
returned: always
type: int
sample: 1000
requests:
description: Number of requests in the last 24 hours
returned: always
type: int
sample: 10
bytes_in:
description: Inbound CDN bandwidth for the last 24 hours in bytes
returned: always
type: int
sample: 1000
containers:
description: Number of containers
returned: always
type: int
sample: 10
bytes:
description: Space used in bytes
returned: always
type: int
sample: 3860997965
objs:
description: Number of objects
returned: always
type: int
sample: 1000
bandwidth:
description: Dictionary of CDN bandwidth facts
returned: always
type: complex
contains:
bytes_out:
description: Outbound bandwidth for the last 24 hours in bytes
returned: always
type: int
sample: 1000
requests:
description: Number of requests in the last 24 hours
returned: always
type: int
sample: 10
bytes_in:
description: Inbound bandwidth for the last 24 hours in bytes
returned: always
type: int
sample: 1000
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.memset import memset_api_call
def get_facts(args=None):
'''
Performs a simple API call and returns a JSON blob.
'''
retvals, payload = dict(), dict()
has_changed, has_failed = False, False
msg, stderr, memset_api = None, None, None
payload['name'] = args['name']
api_method = 'memstore.usage'
has_failed, msg, response = memset_api_call(api_key=args['api_key'], api_method=api_method, payload=payload)
if has_failed:
# this is the first time the API is called; incorrect credentials will
# manifest themselves at this point so we need to ensure the user is
# informed of the reason.
retvals['failed'] = has_failed
retvals['msg'] = msg
retvals['stderr'] = "API returned an error: {0}" . format(response.status_code)
return(retvals)
# we don't want to return the same thing twice
msg = None
memset_api = response.json()
retvals['changed'] = has_changed
retvals['failed'] = has_failed
for val in ['msg', 'memset_api']:
if val is not None:
retvals[val] = eval(val)
return(retvals)
def main():
global module
module = AnsibleModule(
argument_spec=dict(
api_key=dict(required=True, type='str', no_log=True),
name=dict(required=True, type='str')
),
supports_check_mode=False
)
if module._name == 'memset_memstore_facts':
module.deprecate("The 'memset_memstore_facts' module has been renamed to 'memset_memstore_info'", version='2.13')
# populate the dict with the user-provided vars.
args = dict()
for key, arg in module.params.items():
args[key] = arg
retvals = get_facts(args)
if retvals['failed']:
module.fail_json(**retvals)
else:
module.exit_json(**retvals)
if __name__ == '__main__':
main()

View file

@ -0,0 +1 @@
memset_server_info.py

View file

@ -0,0 +1,300 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2018, Simon Weald <ansible@simonweald.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: memset_server_info
author: "Simon Weald (@glitchcrab)"
short_description: Retrieve server information.
notes:
- An API key generated via the Memset customer control panel is needed with the
following minimum scope - I(server.info).
description:
- Retrieve server information.
- This module was called C(memset_server_facts) before Ansible 2.9. The usage did not change.
options:
api_key:
required: true
description:
- The API key obtained from the Memset control panel.
name:
required: true
description:
- The server product name (i.e. C(testyaa1)).
'''
EXAMPLES = '''
- name: get details for testyaa1
memset_server_info:
name: testyaa1
api_key: 5eb86c9896ab03919abcf03857163741
delegate_to: localhost
'''
RETURN = '''
---
memset_api:
description: Info from the Memset API
returned: always
type: complex
contains:
backups:
description: Whether this server has a backup service.
returned: always
type: bool
sample: true
control_panel:
description: Whether the server has a control panel (i.e. cPanel).
returned: always
type: str
sample: 'cpanel'
data_zone:
description: The data zone the server is in.
returned: always
type: str
sample: 'Memset Public Cloud'
expiry_date:
description: Current expiry date of the server.
returned: always
type: str
sample: '2018-08-10'
firewall_rule_group:
description: Details about the firewall group this server is in.
returned: always
type: dict
sample: {
"default_outbound_policy": "RETURN",
"name": "testyaa-fw1",
"nickname": "testyaa cPanel rules",
"notes": "",
"public": false,
"rules": {
"51d7db54d39c3544ef7c48baa0b9944f": {
"action": "ACCEPT",
"comment": "",
"dest_ip6s": "any",
"dest_ips": "any",
"dest_ports": "any",
"direction": "Inbound",
"ip_version": "any",
"ordering": 2,
"protocols": "icmp",
"rule_group_name": "testyaa-fw1",
"rule_id": "51d7db54d39c3544ef7c48baa0b9944f",
"source_ip6s": "any",
"source_ips": "any",
"source_ports": "any"
}
}
}
firewall_type:
description: The type of firewall the server has (i.e. self-managed, managed).
returned: always
type: str
sample: 'managed'
host_name:
description: The server's hostname.
returned: always
type: str
sample: 'testyaa1.miniserver.com'
ignore_monitoring_off:
description: When true, Memset won't remind the customer that monitoring is disabled.
returned: always
type: bool
sample: true
ips:
description: List of dictionaries of all IP addresses assigned to the server.
returned: always
type: list
sample: [
{
"address": "1.2.3.4",
"bytes_in_today": 1000.0,
"bytes_in_yesterday": 2000.0,
"bytes_out_today": 1000.0,
"bytes_out_yesterday": 2000.0
}
]
monitor:
description: Whether the server has monitoring enabled.
returned: always
type: bool
sample: true
monitoring_level:
description: The server's monitoring level (i.e. basic).
returned: always
type: str
sample: 'basic'
name:
description: Server name (same as the service name).
returned: always
type: str
sample: 'testyaa1'
network_zones:
description: The network zone(s) the server is in.
returned: always
type: list
sample: [ 'reading' ]
nickname:
description: Customer-set nickname for the server.
returned: always
type: str
sample: 'database server'
no_auto_reboot:
description: Whether or not to reboot the server if monitoring detects it down.
returned: always
type: bool
sample: true
no_nrpe:
description: Whether Memset should use NRPE to monitor this server.
returned: always
type: bool
sample: true
os:
description: The server's Operating System.
returned: always
type: str
sample: 'debian_stretch_64'
penetration_patrol:
description: Intrusion detection support level for this server.
returned: always
type: str
sample: 'managed'
penetration_patrol_alert_level:
description: The alert level at which notifications are sent.
returned: always
type: int
sample: 10
primary_ip:
description: Server's primary IP.
returned: always
type: str
sample: '1.2.3.4'
renewal_price_amount:
description: Renewal cost for the server.
returned: always
type: str
sample: '30.00'
renewal_price_currency:
description: Currency for renewal payments.
returned: always
type: str
sample: 'GBP'
renewal_price_vat:
description: VAT rate for renewal payments
returned: always
type: str
sample: '20'
start_date:
description: Server's start date.
returned: always
type: str
sample: '2013-04-10'
status:
description: Current status of the server (i.e. live, onhold).
returned: always
type: str
sample: 'LIVE'
support_level:
description: Support level included with the server.
returned: always
type: str
sample: 'managed'
type:
description: What this server is (i.e. dedicated)
returned: always
type: str
sample: 'miniserver'
vlans:
description: Dictionary of tagged and untagged VLANs this server is in.
returned: always
type: dict
sample: {
tagged: [],
untagged: [ 'testyaa-vlan1', 'testyaa-vlan2' ]
}
vulnscan:
description: Vulnerability scanning level.
returned: always
type: str
sample: 'basic'
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.memset import memset_api_call
def get_facts(args=None):
'''
Performs a simple API call and returns a JSON blob.
'''
retvals, payload = dict(), dict()
has_changed, has_failed = False, False
msg, stderr, memset_api = None, None, None
payload['name'] = args['name']
api_method = 'server.info'
has_failed, msg, response = memset_api_call(api_key=args['api_key'], api_method=api_method, payload=payload)
if has_failed:
# this is the first time the API is called; incorrect credentials will
# manifest themselves at this point so we need to ensure the user is
# informed of the reason.
retvals['failed'] = has_failed
retvals['msg'] = msg
retvals['stderr'] = "API returned an error: {0}" . format(response.status_code)
return(retvals)
# we don't want to return the same thing twice
msg = None
memset_api = response.json()
retvals['changed'] = has_changed
retvals['failed'] = has_failed
for val in ['msg', 'memset_api']:
if val is not None:
retvals[val] = eval(val)
return(retvals)
def main():
global module
module = AnsibleModule(
argument_spec=dict(
api_key=dict(required=True, type='str', no_log=True),
name=dict(required=True, type='str')
),
supports_check_mode=False
)
if module._name == 'memset_server_facts':
module.deprecate("The 'memset_server_facts' module has been renamed to 'memset_server_info'", version='2.13')
# populate the dict with the user-provided vars.
args = dict()
for key, arg in module.params.items():
args[key] = arg
retvals = get_facts(args)
if retvals['failed']:
module.fail_json(**retvals)
else:
module.exit_json(**retvals)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,313 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2018, Simon Weald <ansible@simonweald.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: memset_zone
author: "Simon Weald (@glitchcrab)"
short_description: Creates and deletes Memset DNS zones.
notes:
- Zones can be thought of as a logical group of domains, all of which share the
same DNS records (i.e. they point to the same IP). An API key generated via the
Memset customer control panel is needed with the following minimum scope -
I(dns.zone_create), I(dns.zone_delete), I(dns.zone_list).
description:
- Manage DNS zones in a Memset account.
options:
state:
required: true
description:
- Indicates desired state of resource.
choices: [ absent, present ]
api_key:
required: true
description:
- The API key obtained from the Memset control panel.
name:
required: true
description:
- The zone nickname; usually the same as the main domain. Ensure this
value has at most 250 characters.
aliases: [ nickname ]
ttl:
description:
- The default TTL for all records created in the zone. This must be a
valid int from U(https://www.memset.com/apidocs/methods_dns.html#dns.zone_create).
choices: [ 0, 300, 600, 900, 1800, 3600, 7200, 10800, 21600, 43200, 86400 ]
force:
required: false
default: false
type: bool
description:
- Forces deletion of a zone and all zone domains/zone records it contains.
'''
EXAMPLES = '''
# Create the zone 'test'
- name: create zone
memset_zone:
name: test
state: present
api_key: 5eb86c9196ab03919abcf03857163741
ttl: 300
delegate_to: localhost
# Force zone deletion
- name: force delete zone
memset_zone:
name: test
state: absent
api_key: 5eb86c9196ab03919abcf03857163741
force: true
delegate_to: localhost
'''
RETURN = '''
memset_api:
description: Zone info from the Memset API
returned: when state == present
type: complex
contains:
domains:
description: List of domains in this zone
returned: always
type: list
sample: []
id:
description: Zone id
returned: always
type: str
sample: "b0bb1ce851aeea6feeb2dc32fe83bf9c"
nickname:
description: Zone name
returned: always
type: str
sample: "example.com"
records:
description: List of DNS records for domains in this zone
returned: always
type: list
sample: []
ttl:
description: Default TTL for domains in this zone
returned: always
type: int
sample: 300
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.memset import check_zone
from ansible_collections.community.general.plugins.module_utils.memset import get_zone_id
from ansible_collections.community.general.plugins.module_utils.memset import memset_api_call
def api_validation(args=None):
'''
Perform some validation which will be enforced by Memset's API (see:
https://www.memset.com/apidocs/methods_dns.html#dns.zone_record_create)
'''
# zone domain length must be less than 250 chars.
if len(args['name']) > 250:
stderr = 'Zone name must be less than 250 characters in length.'
module.fail_json(failed=True, msg=stderr, stderr=stderr)
def check(args=None):
'''
Support for running with check mode.
'''
retvals = dict()
api_method = 'dns.zone_list'
has_failed, _msg, response = memset_api_call(api_key=args['api_key'], api_method=api_method)
zone_exists, counter = check_zone(data=response, name=args['name'])
# set changed to true if the operation would cause a change.
has_changed = ((zone_exists and args['state'] == 'absent') or (not zone_exists and args['state'] == 'present'))
retvals['changed'] = has_changed
retvals['failed'] = has_failed
return(retvals)
def create_zone(args=None, zone_exists=None, payload=None):
'''
At this point we already know whether the zone exists, so we
just need to make the API reflect the desired state.
'''
has_changed, has_failed = False, False
msg, memset_api = None, None
if not zone_exists:
payload['ttl'] = args['ttl']
payload['nickname'] = args['name']
api_method = 'dns.zone_create'
has_failed, msg, response = memset_api_call(api_key=args['api_key'], api_method=api_method, payload=payload)
if not has_failed:
has_changed = True
else:
api_method = 'dns.zone_list'
_has_failed, _msg, response = memset_api_call(api_key=args['api_key'], api_method=api_method)
for zone in response.json():
if zone['nickname'] == args['name']:
break
if zone['ttl'] != args['ttl']:
# update the zone if the desired TTL is different.
payload['id'] = zone['id']
payload['ttl'] = args['ttl']
api_method = 'dns.zone_update'
has_failed, msg, response = memset_api_call(api_key=args['api_key'], api_method=api_method, payload=payload)
if not has_failed:
has_changed = True
# populate return var with zone info.
api_method = 'dns.zone_list'
_has_failed, _msg, response = memset_api_call(api_key=args['api_key'], api_method=api_method)
zone_exists, msg, counter, zone_id = get_zone_id(zone_name=args['name'], current_zones=response.json())
if zone_exists:
payload = dict()
payload['id'] = zone_id
api_method = 'dns.zone_info'
_has_failed, _msg, response = memset_api_call(api_key=args['api_key'], api_method=api_method, payload=payload)
memset_api = response.json()
return(has_failed, has_changed, memset_api, msg)
def delete_zone(args=None, zone_exists=None, payload=None):
'''
Deletion requires extra sanity checking as the zone cannot be
deleted if it contains domains or records. Setting force=true
will override this behaviour.
'''
has_changed, has_failed = False, False
msg, memset_api = None, None
if zone_exists:
api_method = 'dns.zone_list'
_has_failed, _msg, response = memset_api_call(api_key=args['api_key'], api_method=api_method, payload=payload)
counter = 0
for zone in response.json():
if zone['nickname'] == args['name']:
counter += 1
if counter == 1:
for zone in response.json():
if zone['nickname'] == args['name']:
zone_id = zone['id']
domain_count = len(zone['domains'])
record_count = len(zone['records'])
if (domain_count > 0 or record_count > 0) and args['force'] is False:
# we need to fail out if force was not explicitly set.
stderr = 'Zone contains domains or records and force was not used.'
has_failed = True
has_changed = False
module.fail_json(failed=has_failed, changed=has_changed, msg=msg, stderr=stderr, rc=1)
api_method = 'dns.zone_delete'
payload['id'] = zone_id
has_failed, msg, response = memset_api_call(api_key=args['api_key'], api_method=api_method, payload=payload)
if not has_failed:
has_changed = True
# return raw JSON from API in named var and then unset msg var so we aren't returning the same thing twice.
memset_api = msg
msg = None
else:
# zone names are not unique, so we cannot safely delete the requested
# zone at this time.
has_failed = True
has_changed = False
msg = 'Unable to delete zone as multiple zones with the same name exist.'
else:
has_failed, has_changed = False, False
return(has_failed, has_changed, memset_api, msg)
def create_or_delete(args=None):
'''
We need to perform some initial sanity checking and also look
up required info before handing it off to create or delete.
'''
retvals, payload = dict(), dict()
has_failed, has_changed = False, False
msg, memset_api, stderr = None, None, None
# get the zones and check if the relevant zone exists.
api_method = 'dns.zone_list'
_has_failed, _msg, response = memset_api_call(api_key=args['api_key'], api_method=api_method)
if _has_failed:
# this is the first time the API is called; incorrect credentials will
# manifest themselves at this point so we need to ensure the user is
# informed of the reason.
retvals['failed'] = _has_failed
retvals['msg'] = _msg
return(retvals)
zone_exists, _msg, counter, _zone_id = get_zone_id(zone_name=args['name'], current_zones=response.json())
if args['state'] == 'present':
has_failed, has_changed, memset_api, msg = create_zone(args=args, zone_exists=zone_exists, payload=payload)
elif args['state'] == 'absent':
has_failed, has_changed, memset_api, msg = delete_zone(args=args, zone_exists=zone_exists, payload=payload)
retvals['failed'] = has_failed
retvals['changed'] = has_changed
for val in ['msg', 'stderr', 'memset_api']:
if val is not None:
retvals[val] = eval(val)
return(retvals)
def main():
global module
module = AnsibleModule(
argument_spec=dict(
state=dict(required=True, choices=['present', 'absent'], type='str'),
api_key=dict(required=True, type='str', no_log=True),
name=dict(required=True, aliases=['nickname'], type='str'),
ttl=dict(required=False, default=0, choices=[0, 300, 600, 900, 1800, 3600, 7200, 10800, 21600, 43200, 86400], type='int'),
force=dict(required=False, default=False, type='bool')
),
supports_check_mode=True
)
# populate the dict with the user-provided vars.
args = dict()
for key, arg in module.params.items():
args[key] = arg
args['check_mode'] = module.check_mode
# validate some API-specific limitations.
api_validation(args=args)
if module.check_mode:
retvals = check(args)
else:
retvals = create_or_delete(args)
if retvals['failed']:
module.fail_json(**retvals)
else:
module.exit_json(**retvals)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,268 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2018, Simon Weald <ansible@simonweald.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: memset_zone_domain
author: "Simon Weald (@glitchcrab)"
short_description: Create and delete domains in Memset DNS zones.
notes:
- Zone domains can be thought of as a collection of domains, all of which share the
same DNS records (i.e. they point to the same IP). An API key generated via the
Memset customer control panel is needed with the following minimum scope -
I(dns.zone_domain_create), I(dns.zone_domain_delete), I(dns.zone_domain_list).
- Currently this module can only create one domain at a time. Multiple domains should
be created using C(with_items).
description:
- Manage DNS zone domains in a Memset account.
options:
state:
default: present
description:
- Indicates desired state of resource.
choices: [ absent, present ]
api_key:
required: true
description:
- The API key obtained from the Memset control panel.
domain:
required: true
description:
- The zone domain name. Ensure this value has at most 250 characters.
aliases: ['name']
zone:
required: true
description:
- The zone to add the domain to (this must already exist).
'''
EXAMPLES = '''
# Create the zone domain 'test.com'
- name: create zone domain
memset_zone_domain:
domain: test.com
zone: testzone
state: present
api_key: 5eb86c9196ab03919abcf03857163741
delegate_to: localhost
'''
RETURN = '''
memset_api:
description: Domain info from the Memset API
returned: when changed or state == present
type: complex
contains:
domain:
description: Domain name
returned: always
type: str
sample: "example.com"
id:
description: Domain ID
returned: always
type: str
sample: "b0bb1ce851aeea6feeb2dc32fe83bf9c"
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.memset import get_zone_id
from ansible_collections.community.general.plugins.module_utils.memset import check_zone_domain
from ansible_collections.community.general.plugins.module_utils.memset import memset_api_call
def api_validation(args=None):
'''
Perform some validation which will be enforced by Memset's API (see:
https://www.memset.com/apidocs/methods_dns.html#dns.zone_domain_create)
'''
# zone domain length must be less than 250 chars
if len(args['domain']) > 250:
stderr = 'Zone domain must be less than 250 characters in length.'
module.fail_json(failed=True, msg=stderr)
def check(args=None):
'''
Support for running with check mode.
'''
retvals = dict()
has_changed = False
api_method = 'dns.zone_domain_list'
has_failed, msg, response = memset_api_call(api_key=args['api_key'], api_method=api_method)
domain_exists = check_zone_domain(data=response, domain=args['domain'])
# set changed to true if the operation would cause a change.
has_changed = ((domain_exists and args['state'] == 'absent') or (not domain_exists and args['state'] == 'present'))
retvals['changed'] = has_changed
retvals['failed'] = has_failed
return(retvals)
def create_zone_domain(args=None, zone_exists=None, zone_id=None, payload=None):
'''
At this point we already know whether the containing zone exists,
so we just need to create the domain (or exit if it already exists).
'''
has_changed, has_failed = False, False
msg = None
api_method = 'dns.zone_domain_list'
_has_failed, _msg, response = memset_api_call(api_key=args['api_key'], api_method=api_method)
for zone_domain in response.json():
if zone_domain['domain'] == args['domain']:
# zone domain already exists, nothing to change.
has_changed = False
break
else:
# we need to create the domain
api_method = 'dns.zone_domain_create'
payload['domain'] = args['domain']
payload['zone_id'] = zone_id
has_failed, msg, response = memset_api_call(api_key=args['api_key'], api_method=api_method, payload=payload)
if not has_failed:
has_changed = True
return(has_failed, has_changed, msg)
def delete_zone_domain(args=None, payload=None):
'''
Deletion is pretty simple, domains are always unique so we
we don't need to do any sanity checking to avoid deleting the
wrong thing.
'''
has_changed, has_failed = False, False
msg, memset_api = None, None
api_method = 'dns.zone_domain_list'
_has_failed, _msg, response = memset_api_call(api_key=args['api_key'], api_method=api_method)
domain_exists = check_zone_domain(data=response, domain=args['domain'])
if domain_exists:
api_method = 'dns.zone_domain_delete'
payload['domain'] = args['domain']
has_failed, msg, response = memset_api_call(api_key=args['api_key'], api_method=api_method, payload=payload)
if not has_failed:
has_changed = True
memset_api = response.json()
# unset msg as we don't want to return unnecessary info to the user.
msg = None
return(has_failed, has_changed, memset_api, msg)
def create_or_delete_domain(args=None):
'''
We need to perform some initial sanity checking and also look
up required info before handing it off to create or delete.
'''
retvals, payload = dict(), dict()
has_changed, has_failed = False, False
msg, stderr, memset_api = None, None, None
# get the zones and check if the relevant zone exists.
api_method = 'dns.zone_list'
has_failed, msg, response = memset_api_call(api_key=args['api_key'], api_method=api_method)
if has_failed:
# this is the first time the API is called; incorrect credentials will
# manifest themselves at this point so we need to ensure the user is
# informed of the reason.
retvals['failed'] = has_failed
retvals['msg'] = msg
retvals['stderr'] = "API returned an error: {0}" . format(response.status_code)
return(retvals)
zone_exists, msg, counter, zone_id = get_zone_id(zone_name=args['zone'], current_zones=response.json())
if not zone_exists:
# the zone needs to be unique - this isn't a requirement of Memset's API but it
# makes sense in the context of this module.
has_failed = True
if counter == 0:
stderr = "DNS zone '{0}' does not exist, cannot create domain." . format(args['zone'])
elif counter > 1:
stderr = "{0} matches multiple zones, cannot create domain." . format(args['zone'])
retvals['failed'] = has_failed
retvals['msg'] = stderr
return(retvals)
if args['state'] == 'present':
has_failed, has_changed, msg = create_zone_domain(args=args, zone_exists=zone_exists, zone_id=zone_id, payload=payload)
if args['state'] == 'absent':
has_failed, has_changed, memset_api, msg = delete_zone_domain(args=args, payload=payload)
retvals['changed'] = has_changed
retvals['failed'] = has_failed
for val in ['msg', 'stderr', 'memset_api']:
if val is not None:
retvals[val] = eval(val)
return(retvals)
def main():
global module
module = AnsibleModule(
argument_spec=dict(
state=dict(default='present', choices=['present', 'absent'], type='str'),
api_key=dict(required=True, type='str', no_log=True),
domain=dict(required=True, aliases=['name'], type='str'),
zone=dict(required=True, type='str')
),
supports_check_mode=True
)
# populate the dict with the user-provided vars.
args = dict()
for key, arg in module.params.items():
args[key] = arg
args['check_mode'] = module.check_mode
# validate some API-specific limitations.
api_validation(args=args)
if module.check_mode:
retvals = check(args)
else:
retvals = create_or_delete_domain(args)
# we would need to populate the return values with the API's response
# in several places so it's easier to do it at the end instead.
if not retvals['failed']:
if args['state'] == 'present' and not module.check_mode:
payload = dict()
payload['domain'] = args['domain']
api_method = 'dns.zone_domain_info'
_has_failed, _msg, response = memset_api_call(api_key=args['api_key'], api_method=api_method, payload=payload)
retvals['memset_api'] = response.json()
if retvals['failed']:
module.fail_json(**retvals)
else:
module.exit_json(**retvals)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,377 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2018, Simon Weald <ansible@simonweald.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: memset_zone_record
author: "Simon Weald (@glitchcrab)"
short_description: Create and delete records in Memset DNS zones.
notes:
- Zones can be thought of as a logical group of domains, all of which share the
same DNS records (i.e. they point to the same IP). An API key generated via the
Memset customer control panel is needed with the following minimum scope -
I(dns.zone_create), I(dns.zone_delete), I(dns.zone_list).
- Currently this module can only create one DNS record at a time. Multiple records
should be created using C(with_items).
description:
- Manage DNS records in a Memset account.
options:
state:
default: present
description:
- Indicates desired state of resource.
choices: [ absent, present ]
api_key:
required: true
description:
- The API key obtained from the Memset control panel.
address:
required: true
description:
- The address for this record (can be IP or text string depending on record type).
aliases: [ ip, data ]
priority:
description:
- C(SRV) and C(TXT) record priority, in the range 0 > 999 (inclusive).
record:
required: false
description:
- The subdomain to create.
type:
required: true
description:
- The type of DNS record to create.
choices: [ A, AAAA, CNAME, MX, NS, SRV, TXT ]
relative:
type: bool
description:
- If set then the current domain is added onto the address field for C(CNAME), C(MX), C(NS)
and C(SRV)record types.
ttl:
description:
- The record's TTL in seconds (will inherit zone's TTL if not explicitly set). This must be a
valid int from U(https://www.memset.com/apidocs/methods_dns.html#dns.zone_record_create).
choices: [ 0, 300, 600, 900, 1800, 3600, 7200, 10800, 21600, 43200, 86400 ]
zone:
required: true
description:
- The name of the zone to which to add the record to.
'''
EXAMPLES = '''
# Create DNS record for www.domain.com
- name: create DNS record
memset_zone_record:
api_key: dcf089a2896940da9ffefb307ef49ccd
state: present
zone: domain.com
type: A
record: www
address: 1.2.3.4
ttl: 300
relative: false
delegate_to: localhost
# create an SPF record for domain.com
- name: create SPF record for domain.com
memset_zone_record:
api_key: dcf089a2896940da9ffefb307ef49ccd
state: present
zone: domain.com
type: TXT
address: "v=spf1 +a +mx +ip4:a1.2.3.4 ?all"
delegate_to: localhost
# create multiple DNS records
- name: create multiple DNS records
memset_zone_record:
api_key: dcf089a2896940da9ffefb307ef49ccd
zone: "{{ item.zone }}"
type: "{{ item.type }}"
record: "{{ item.record }}"
address: "{{ item.address }}"
delegate_to: localhost
with_items:
- { 'zone': 'domain1.com', 'type': 'A', 'record': 'www', 'address': '1.2.3.4' }
- { 'zone': 'domain2.com', 'type': 'A', 'record': 'mail', 'address': '4.3.2.1' }
'''
RETURN = '''
memset_api:
description: Record info from the Memset API.
returned: when state == present
type: complex
contains:
address:
description: Record content (may be an IP, string or blank depending on record type).
returned: always
type: str
sample: 1.1.1.1
id:
description: Record ID.
returned: always
type: str
sample: "b0bb1ce851aeea6feeb2dc32fe83bf9c"
priority:
description: Priority for C(MX) and C(SRV) records.
returned: always
type: int
sample: 10
record:
description: Name of record.
returned: always
type: str
sample: "www"
relative:
description: Adds the current domain onto the address field for C(CNAME), C(MX), C(NS) and C(SRV) types.
returned: always
type: bool
sample: False
ttl:
description: Record TTL.
returned: always
type: int
sample: 10
type:
description: Record type.
returned: always
type: str
sample: AAAA
zone_id:
description: Zone ID.
returned: always
type: str
sample: "b0bb1ce851aeea6feeb2dc32fe83bf9c"
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.memset import get_zone_id
from ansible_collections.community.general.plugins.module_utils.memset import memset_api_call
from ansible_collections.community.general.plugins.module_utils.memset import get_zone_id
def api_validation(args=None):
'''
Perform some validation which will be enforced by Memset's API (see:
https://www.memset.com/apidocs/methods_dns.html#dns.zone_record_create)
'''
failed_validation = False
# priority can only be integer 0 > 999
if not 0 <= args['priority'] <= 999:
failed_validation = True
error = 'Priority must be in the range 0 > 999 (inclusive).'
# data value must be max 250 chars
if len(args['address']) > 250:
failed_validation = True
error = "Address must be less than 250 characters in length."
# record value must be max 250 chars
if args['record']:
if len(args['record']) > 63:
failed_validation = True
error = "Record must be less than 63 characters in length."
# relative isn't used for all record types
if args['relative']:
if args['type'] not in ['CNAME', 'MX', 'NS', 'SRV']:
failed_validation = True
error = "Relative is only valid for CNAME, MX, NS and SRV record types."
# if any of the above failed then fail early
if failed_validation:
module.fail_json(failed=True, msg=error)
def create_zone_record(args=None, zone_id=None, records=None, payload=None):
'''
Sanity checking has already occurred prior to this function being
called, so we can go ahead and either create or update the record.
As defaults are defined for all values in the argument_spec, this
may cause some changes to occur as the defaults are enforced (if
the user has only configured required variables).
'''
has_changed, has_failed = False, False
msg, memset_api = None, None
# assemble the new record.
new_record = dict()
new_record['zone_id'] = zone_id
for arg in ['priority', 'address', 'relative', 'record', 'ttl', 'type']:
new_record[arg] = args[arg]
# if we have any matches, update them.
if records:
for zone_record in records:
# record exists, add ID to payload.
new_record['id'] = zone_record['id']
if zone_record == new_record:
# nothing to do; record is already correct so we populate
# the return var with the existing record's details.
memset_api = zone_record
return(has_changed, has_failed, memset_api, msg)
else:
# merge dicts ensuring we change any updated values
payload = zone_record.copy()
payload.update(new_record)
api_method = 'dns.zone_record_update'
if args['check_mode']:
has_changed = True
# return the new record to the user in the returned var.
memset_api = new_record
return(has_changed, has_failed, memset_api, msg)
has_failed, msg, response = memset_api_call(api_key=args['api_key'], api_method=api_method, payload=payload)
if not has_failed:
has_changed = True
memset_api = new_record
# empty msg as we don't want to return a boatload of json to the user.
msg = None
else:
# no record found, so we need to create it
api_method = 'dns.zone_record_create'
payload = new_record
if args['check_mode']:
has_changed = True
# populate the return var with the new record's details.
memset_api = new_record
return(has_changed, has_failed, memset_api, msg)
has_failed, msg, response = memset_api_call(api_key=args['api_key'], api_method=api_method, payload=payload)
if not has_failed:
has_changed = True
memset_api = new_record
# empty msg as we don't want to return a boatload of json to the user.
msg = None
return(has_changed, has_failed, memset_api, msg)
def delete_zone_record(args=None, records=None, payload=None):
'''
Matching records can be cleanly deleted without affecting other
resource types, so this is pretty simple to achieve.
'''
has_changed, has_failed = False, False
msg, memset_api = None, None
# if we have any matches, delete them.
if records:
for zone_record in records:
if args['check_mode']:
has_changed = True
return(has_changed, has_failed, memset_api, msg)
payload['id'] = zone_record['id']
api_method = 'dns.zone_record_delete'
has_failed, msg, response = memset_api_call(api_key=args['api_key'], api_method=api_method, payload=payload)
if not has_failed:
has_changed = True
memset_api = zone_record
# empty msg as we don't want to return a boatload of json to the user.
msg = None
return(has_changed, has_failed, memset_api, msg)
def create_or_delete(args=None):
'''
We need to perform some initial sanity checking and also look
up required info before handing it off to create or delete functions.
Check mode is integrated into the create or delete functions.
'''
has_failed, has_changed = False, False
msg, memset_api, stderr = None, None, None
retvals, payload = dict(), dict()
# get the zones and check if the relevant zone exists.
api_method = 'dns.zone_list'
_has_failed, msg, response = memset_api_call(api_key=args['api_key'], api_method=api_method)
if _has_failed:
# this is the first time the API is called; incorrect credentials will
# manifest themselves at this point so we need to ensure the user is
# informed of the reason.
retvals['failed'] = _has_failed
retvals['msg'] = msg
retvals['stderr'] = "API returned an error: {0}" . format(response.status_code)
return(retvals)
zone_exists, _msg, counter, zone_id = get_zone_id(zone_name=args['zone'], current_zones=response.json())
if not zone_exists:
has_failed = True
if counter == 0:
stderr = "DNS zone {0} does not exist." . format(args['zone'])
elif counter > 1:
stderr = "{0} matches multiple zones." . format(args['zone'])
retvals['failed'] = has_failed
retvals['msg'] = stderr
retvals['stderr'] = stderr
return(retvals)
# get a list of all records ( as we can't limit records by zone)
api_method = 'dns.zone_record_list'
_has_failed, _msg, response = memset_api_call(api_key=args['api_key'], api_method=api_method)
# find any matching records
records = [record for record in response.json() if record['zone_id'] == zone_id
and record['record'] == args['record'] and record['type'] == args['type']]
if args['state'] == 'present':
has_changed, has_failed, memset_api, msg = create_zone_record(args=args, zone_id=zone_id, records=records, payload=payload)
if args['state'] == 'absent':
has_changed, has_failed, memset_api, msg = delete_zone_record(args=args, records=records, payload=payload)
retvals['changed'] = has_changed
retvals['failed'] = has_failed
for val in ['msg', 'stderr', 'memset_api']:
if val is not None:
retvals[val] = eval(val)
return(retvals)
def main():
global module
module = AnsibleModule(
argument_spec=dict(
state=dict(required=False, default='present', choices=['present', 'absent'], type='str'),
api_key=dict(required=True, type='str', no_log=True),
zone=dict(required=True, type='str'),
type=dict(required=True, choices=['A', 'AAAA', 'CNAME', 'MX', 'NS', 'SRV', 'TXT'], type='str'),
address=dict(required=True, aliases=['ip', 'data'], type='str'),
record=dict(required=False, default='', type='str'),
ttl=dict(required=False, default=0, choices=[0, 300, 600, 900, 1800, 3600, 7200, 10800, 21600, 43200, 86400], type='int'),
priority=dict(required=False, default=0, type='int'),
relative=dict(required=False, default=False, type='bool')
),
supports_check_mode=True
)
# populate the dict with the user-provided vars.
args = dict()
for key, arg in module.params.items():
args[key] = arg
args['check_mode'] = module.check_mode
# perform some Memset API-specific validation
api_validation(args=args)
retvals = create_or_delete(args)
if retvals['failed']:
module.fail_json(**retvals)
else:
module.exit_json(**retvals)
if __name__ == '__main__':
main()