summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rwxr-xr-xcontrib/inventory/consul_io.py52
-rw-r--r--docsite/rst/community.rst2
-rw-r--r--docsite/rst/faq.rst4
-rw-r--r--lib/ansible/module_utils/ec2.py113
-rw-r--r--lib/ansible/parsing/splitter.py2
-rw-r--r--lib/ansible/plugins/action/__init__.py10
-rw-r--r--lib/ansible/plugins/action/script.py10
-rw-r--r--lib/ansible/plugins/action/synchronize.py10
-rw-r--r--lib/ansible/plugins/connection/__init__.py6
-rw-r--r--lib/ansible/plugins/connection/accelerate.py44
-rw-r--r--lib/ansible/plugins/connection/docker.py2
-rw-r--r--lib/ansible/plugins/connection/local.py8
-rw-r--r--lib/ansible/plugins/connection/ssh.py2
-rw-r--r--lib/ansible/plugins/filter/ipaddr.py6
-rw-r--r--lib/ansible/plugins/shell/__init__.py4
-rw-r--r--lib/ansible/plugins/shell/powershell.py16
-rw-r--r--lib/ansible/utils/module_docs_fragments/eos.py8
-rw-r--r--lib/ansible/utils/module_docs_fragments/ios.py4
-rw-r--r--lib/ansible/utils/module_docs_fragments/nxos.py4
-rw-r--r--lib/ansible/utils/module_docs_fragments/openswitch.py4
-rw-r--r--test/integration/Makefile5
-rw-r--r--test/integration/cloudflare.yml8
-rw-r--r--test/integration/credentials.template5
-rw-r--r--test/integration/roles/test_cloudflare_dns/defaults/main.yml2
-rw-r--r--test/integration/roles/test_cloudflare_dns/tasks/a_record.yml180
-rw-r--r--test/integration/roles/test_cloudflare_dns/tasks/aaaa_record.yml180
-rw-r--r--test/integration/roles/test_cloudflare_dns/tasks/cname_record.yml139
-rw-r--r--test/integration/roles/test_cloudflare_dns/tasks/main.yml64
-rw-r--r--test/integration/roles/test_cloudflare_dns/tasks/mx_record.yml194
-rw-r--r--test/integration/roles/test_cloudflare_dns/tasks/ns_record.yml182
-rw-r--r--test/integration/roles/test_cloudflare_dns/tasks/spf_record.yml184
-rw-r--r--test/integration/roles/test_cloudflare_dns/tasks/srv_record.yml266
-rw-r--r--test/integration/roles/test_cloudflare_dns/tasks/txt_record.yml184
-rw-r--r--test/utils/tox/requirements.txt1
35 files changed, 1847 insertions, 59 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0d0adb4d3a..8f3cb98e10 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -39,6 +39,7 @@ Ansible Changes By Release
####New Filters:
* extract
+* ip4_hex
####New Callbacks:
* actionable (only shows changed and failed)
diff --git a/contrib/inventory/consul_io.py b/contrib/inventory/consul_io.py
index 1bcf22d373..5af7188a52 100755
--- a/contrib/inventory/consul_io.py
+++ b/contrib/inventory/consul_io.py
@@ -129,6 +129,58 @@ import sys
import ConfigParser
import urllib, urllib2, base64
+
+def get_log_filename():
+ tty_filename = '/dev/tty'
+ stdout_filename = '/dev/stdout'
+
+ if not os.path.exists(tty_filename):
+ return stdout_filename
+ if not os.access(tty_filename, os.W_OK):
+ return stdout_filename
+ if os.getenv('TEAMCITY_VERSION'):
+ return stdout_filename
+
+ return tty_filename
+
+
+def setup_logging():
+ filename = get_log_filename()
+
+ import logging.config
+ logging.config.dictConfig({
+ 'version': 1,
+ 'formatters': {
+ 'simple': {
+ 'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s',
+ },
+ },
+ 'root': {
+ 'level': os.getenv('ANSIBLE_INVENTORY_CONSUL_IO_LOG_LEVEL', 'WARN'),
+ 'handlers': ['console'],
+ },
+ 'handlers': {
+ 'console': {
+ 'class': 'logging.FileHandler',
+ 'filename': filename,
+ 'formatter': 'simple',
+ },
+ },
+ 'loggers': {
+ 'iso8601': {
+ 'qualname': 'iso8601',
+ 'level': 'INFO',
+ },
+ },
+ })
+ logger = logging.getLogger('consul_io.py')
+ logger.debug('Invoked with %r', sys.argv)
+
+
+if os.getenv('ANSIBLE_INVENTORY_CONSUL_IO_LOG_ENABLED'):
+ setup_logging()
+
+
try:
import json
except ImportError:
diff --git a/docsite/rst/community.rst b/docsite/rst/community.rst
index 66543c6245..79984ca58c 100644
--- a/docsite/rst/community.rst
+++ b/docsite/rst/community.rst
@@ -68,7 +68,7 @@ to see if the issue has already been reported.
MODULE related bugs however should go to `ansible-modules-core <https://github.com/ansible/ansible-modules-core>`_ or `ansible-modules-extras <https://github.com/ansible/ansible-modules-extras>`_ based on the classification of the module. This is listed on the bottom of the docs page for any module.
-When filing a bug, please use the `issue template <https://github.com/ansible/ansible/raw/devel/ISSUE_TEMPLATE.md>`_ to provide all relevant information, regardless of what repo you are filing a ticket against.
+When filing a bug, please use the `issue template <https://github.com/ansible/ansible/raw/devel/.github/ISSUE_TEMPLATE.md>`_ to provide all relevant information, regardless of what repo you are filing a ticket against.
Knowing your ansible version and the exact commands you are running, and what you expect, saves time and helps us help everyone with their issues
more quickly.
diff --git a/docsite/rst/faq.rst b/docsite/rst/faq.rst
index 15ebbc15f9..c946856665 100644
--- a/docsite/rst/faq.rst
+++ b/docsite/rst/faq.rst
@@ -340,11 +340,11 @@ as this made it hard to distinguish between an undefined variable and a string.
Another rule is 'moustaches don't stack'. We often see this::
- {{ somvar_{{other_var}} }}
+ {{ somevar_{{other_var}} }}
The above DOES NOT WORK, if you need to use a dynamic variable use the hostvars or vars dictionary as appropriate::
- {{ hostvars[inventory_hostname]['somevar_' + other_var] }}
+ {{ hostvars[inventory_hostname]['somevar_' + other_var] }}
.. _i_dont_see_my_question:
diff --git a/lib/ansible/module_utils/ec2.py b/lib/ansible/module_utils/ec2.py
index 52b085eb78..e5d807cc8d 100644
--- a/lib/ansible/module_utils/ec2.py
+++ b/lib/ansible/module_utils/ec2.py
@@ -262,3 +262,116 @@ def paging(pause=0):
return page
return wrapper
+
+def camel_dict_to_snake_dict(camel_dict):
+
+ def camel_to_snake(name):
+
+ import re
+
+ first_cap_re = re.compile('(.)([A-Z][a-z]+)')
+ all_cap_re = re.compile('([a-z0-9])([A-Z])')
+ s1 = first_cap_re.sub(r'\1_\2', name)
+
+ return all_cap_re.sub(r'\1_\2', s1).lower()
+
+
+ snake_dict = {}
+ for k, v in camel_dict.iteritems():
+ if isinstance(v, dict):
+ v = camel_dict_to_snake_dict(v)
+ snake_dict[camel_to_snake(k)] = v
+
+ return snake_dict
+
+
+def ansible_dict_to_boto3_filter_list(filters_dict):
+
+ """ Convert an Ansible dict of filters to list of dicts that boto3 can use
+ Args:
+ filters_dict (dict): Dict of AWS filters.
+ Basic Usage:
+ >>> filters = {'some-aws-id', 'i-01234567'}
+ >>> ansible_dict_to_boto3_filter_list(filters)
+ {
+ 'some-aws-id': 'i-01234567'
+ }
+ Returns:
+ List: List of AWS filters and their values
+ [
+ {
+ 'Name': 'some-aws-id',
+ 'Values': [
+ 'i-01234567',
+ ]
+ }
+ ]
+ """
+
+ filters_list = []
+ for k,v in filters_dict.iteritems():
+ filter_dict = {'Name': k}
+ if isinstance(v, basestring):
+ filter_dict['Values'] = [v]
+ else:
+ filter_dict['Values'] = v
+
+ filters_list.append(filter_dict)
+
+ return filters_list
+
+
+def boto3_tag_list_to_ansible_dict(tags_list):
+
+ """ Convert a boto3 list of resource tags to a flat dict of key:value pairs
+ Args:
+ tags_list (list): List of dicts representing AWS tags.
+ Basic Usage:
+ >>> tags_list = [{'Key': 'MyTagKey', 'Value': 'MyTagValue'}]
+ >>> boto3_tag_list_to_ansible_dict(tags_list)
+ [
+ {
+ 'Key': 'MyTagKey',
+ 'Value': 'MyTagValue'
+ }
+ ]
+ Returns:
+ Dict: Dict of key:value pairs representing AWS tags
+ {
+ 'MyTagKey': 'MyTagValue',
+ }
+ """
+
+ tags_dict = {}
+ for tag in tags_list:
+ tags_dict[tag['Key']] = tag['Value']
+
+ return tags_dict
+
+
+def ansible_dict_to_boto3_tag_list(tags_dict):
+
+ """ Convert a flat dict of key:value pairs representing AWS resource tags to a boto3 list of dicts
+ Args:
+ tags_dict (dict): Dict representing AWS resource tags.
+ Basic Usage:
+ >>> tags_dict = {'MyTagKey': 'MyTagValue'}
+ >>> ansible_dict_to_boto3_tag_list(tags_dict)
+ {
+ 'MyTagKey': 'MyTagValue'
+ }
+ Returns:
+ List: List of dicts containing tag keys and values
+ [
+ {
+ 'Key': 'MyTagKey',
+ 'Value': 'MyTagValue'
+ }
+ ]
+ """
+
+ tags_list = []
+ for k,v in tags_dict.iteritems():
+ tags_list.append({'Key': k, 'Value': v})
+
+ return tags_list
diff --git a/lib/ansible/parsing/splitter.py b/lib/ansible/parsing/splitter.py
index 8f3c5cc088..ed5ea55e81 100644
--- a/lib/ansible/parsing/splitter.py
+++ b/lib/ansible/parsing/splitter.py
@@ -256,6 +256,6 @@ def split_args(args):
# If we're done and things are not at zero depth or we're still inside quotes,
# raise an error to indicate that the args were unbalanced
if print_depth or block_depth or comment_depth or inside_quotes:
- raise AnsibleParserError("failed at splitting arguments, either an unbalanced jinja2 block or quotes")
+ raise AnsibleParserError("failed at splitting arguments, either an unbalanced jinja2 block or quotes: {}".format(args))
return params
diff --git a/lib/ansible/plugins/action/__init__.py b/lib/ansible/plugins/action/__init__.py
index 2ba0650de3..fdff04b400 100644
--- a/lib/ansible/plugins/action/__init__.py
+++ b/lib/ansible/plugins/action/__init__.py
@@ -92,6 +92,13 @@ class ActionBase(with_metaclass(ABCMeta, object)):
)
return results
+ def _remote_file_exists(self, path):
+ cmd = self._connection._shell.exists(path)
+ result = self._low_level_execute_command(cmd=cmd, sudoable=True)
+ if result['rc'] == 0:
+ return True
+ return False
+
def _configure_module(self, module_name, module_args, task_vars=None):
'''
Handles the loading and templating of the module code through the
@@ -614,7 +621,8 @@ class ActionBase(with_metaclass(ABCMeta, object)):
if self._connection.allow_executable:
if executable is None:
executable = self._play_context.executable
- cmd = executable + ' -c ' + pipes.quote(cmd)
+ if executable:
+ cmd = executable + ' -c ' + pipes.quote(cmd)
display.debug("_low_level_execute_command(): executing: %s" % (cmd,))
rc, stdout, stderr = self._connection.exec_command(cmd, in_data=in_data, sudoable=sudoable)
diff --git a/lib/ansible/plugins/action/script.py b/lib/ansible/plugins/action/script.py
index c16fc227c4..d8a9ebd5be 100644
--- a/lib/ansible/plugins/action/script.py
+++ b/lib/ansible/plugins/action/script.py
@@ -26,12 +26,6 @@ from ansible.plugins.action import ActionBase
class ActionModule(ActionBase):
TRANSFERS_FILES = True
- def _get_remote_raw_stat(self, path):
- cmd = ['test', '-e', path]
- result = self._low_level_execute_command(cmd=' '.join(cmd), sudoable=True)
- if result['rc'] == 0:
- return True
- return False
def run(self, tmp=None, task_vars=None):
''' handler for file transfer operations '''
@@ -54,7 +48,7 @@ class ActionModule(ActionBase):
# do not run the command if the line contains creates=filename
# and the filename already exists. This allows idempotence
# of command executions.
- if self._get_remote_raw_stat(creates):
+ if self._remote_file_exists(creates):
return dict(skipped=True, msg=("skipped, since %s exists" % creates))
removes = self._task.args.get('removes')
@@ -62,7 +56,7 @@ class ActionModule(ActionBase):
# do not run the command if the line contains removes=filename
# and the filename does not exist. This allows idempotence
# of command executions.
- if self._get_remote_raw_stat(removes):
+ if self._remote_file_exists(removes):
return dict(skipped=True, msg=("skipped, since %s does not exist" % removes))
# the script name is the first item in the raw params, so we split it
diff --git a/lib/ansible/plugins/action/synchronize.py b/lib/ansible/plugins/action/synchronize.py
index 115791759a..0a74ed2d95 100644
--- a/lib/ansible/plugins/action/synchronize.py
+++ b/lib/ansible/plugins/action/synchronize.py
@@ -80,7 +80,7 @@ class ActionModule(ActionBase):
is a different host (for instance, an ssh tunnelled port or an
alternative ssh port to a vagrant host.)
"""
- transport = self._play_context.connection
+ transport = self._connection.transport
if host not in C.LOCALHOST or transport != "local":
if port_matches_localhost_port and host in C.LOCALHOST:
self._task.args['_substitute_controller'] = True
@@ -143,13 +143,13 @@ class ActionModule(ActionBase):
result = super(ActionModule, self).run(tmp, task_vars)
- # self._play_context.connection accounts for delegate_to so
+ # self._connection accounts for delegate_to so
# remote_transport is the transport ansible thought it would need
# between the controller and the delegate_to host or the controller
# and the remote_host if delegate_to isn't set.
remote_transport = False
- if self._play_context.connection != 'local':
+ if self._connection.transport != 'local':
remote_transport = True
try:
@@ -159,9 +159,9 @@ class ActionModule(ActionBase):
# ssh paramiko and local are fully supported transports. Anything
# else only works with delegate_to
- if delegate_to is None and self._play_context.connection not in ('ssh', 'paramiko', 'smart', 'local'):
+ if delegate_to is None and self._connection.transport not in ('ssh', 'paramiko', 'local'):
result['failed'] = True
- result['msg'] = "synchronize uses rsync to function. rsync needs to connect to the remote host via ssh or a direct filesystem copy. This remote host is being accessed via %s instead so it cannot work." % self._play_context.connection
+ result['msg'] = "synchronize uses rsync to function. rsync needs to connect to the remote host via ssh or a direct filesystem copy. This remote host is being accessed via %s instead so it cannot work." % self._connection.transport
return result
use_ssh_args = self._task.args.pop('use_ssh_args', None)
diff --git a/lib/ansible/plugins/connection/__init__.py b/lib/ansible/plugins/connection/__init__.py
index 86f2a3b550..e1b9e959d0 100644
--- a/lib/ansible/plugins/connection/__init__.py
+++ b/lib/ansible/plugins/connection/__init__.py
@@ -263,11 +263,11 @@ class ConnectionBase(with_metaclass(ABCMeta, object)):
def connection_lock(self):
f = self._play_context.connection_lockfd
- display.vvvv('CONNECTION: pid %d waiting for lock on %d' % (os.getpid(), f))
+ display.vvvv('CONNECTION: pid %d waiting for lock on %d' % (os.getpid(), f), host=self._play_context.remote_addr)
fcntl.lockf(f, fcntl.LOCK_EX)
- display.vvvv('CONNECTION: pid %d acquired lock on %d' % (os.getpid(), f))
+ display.vvvv('CONNECTION: pid %d acquired lock on %d' % (os.getpid(), f), host=self._play_context.remote_addr)
def connection_unlock(self):
f = self._play_context.connection_lockfd
fcntl.lockf(f, fcntl.LOCK_UN)
- display.vvvv('CONNECTION: pid %d released lock on %d' % (os.getpid(), f))
+ display.vvvv('CONNECTION: pid %d released lock on %d' % (os.getpid(), f), host=self._play_context.remote_addr)
diff --git a/lib/ansible/plugins/connection/accelerate.py b/lib/ansible/plugins/connection/accelerate.py
index c62cc95b9b..db192a555f 100644
--- a/lib/ansible/plugins/connection/accelerate.py
+++ b/lib/ansible/plugins/connection/accelerate.py
@@ -67,20 +67,20 @@ class Connection(ConnectionBase):
tries = 3
self.conn = socket.socket()
self.conn.settimeout(C.ACCELERATE_CONNECT_TIMEOUT)
- display.vvvv("attempting connection to %s via the accelerated port %d" % (self._play_context.remote_addr,self._play_context.accelerate_port))
+ display.vvvv("attempting connection to %s via the accelerated port %d" % (self._play_context.remote_addr, self._play_context.accelerate_port), host=self._play_context.remote_addr)
while tries > 0:
try:
self.conn.connect((self._play_context.remote_addr,self._play_context.accelerate_port))
break
except socket.error:
- display.vvvv("connection to %s failed, retrying..." % self._play_context.remote_addr)
+ display.vvvv("connection to %s failed, retrying..." % self._play_context.remote_addr, host=self._play_context.remote_addr)
time.sleep(0.1)
tries -= 1
if tries == 0:
- display.vvv("Could not connect via the accelerated connection, exceeded # of tries")
+ display.vvv("Could not connect via the accelerated connection, exceeded # of tries", host=self._play_context.remote_addr)
raise AnsibleConnectionFailure("Failed to connect to %s on the accelerated port %s" % (self._play_context.remote_addr, self._play_context.accelerate_port))
elif wrong_user:
- display.vvv("Restarting daemon with a different remote_user")
+ display.vvv("Restarting daemon with a different remote_user", host=self._play_context.remote_addr)
raise AnsibleError("The accelerated daemon was started on the remote with a different user")
self.conn.settimeout(C.ACCELERATE_TIMEOUT)
@@ -102,25 +102,25 @@ class Connection(ConnectionBase):
header_len = 8 # size of a packed unsigned long long
data = b""
try:
- display.vvvv("%s: in recv_data(), waiting for the header" % self._play_context.remote_addr)
+ display.vvvv("in recv_data(), waiting for the header", host=self._play_context.remote_addr)
while len(data) < header_len:
d = self.conn.recv(header_len - len(data))
if not d:
- display.vvvv("%s: received nothing, bailing out" % self._play_context.remote_addr)
+ display.vvvv("received nothing, bailing out", host=self._play_context.remote_addr)
return None
data += d
- display.vvvv("%s: got the header, unpacking" % self._play_context.remote_addr)
+ display.vvvv("got the header, unpacking", host=self._play_context.remote_addr)
data_len = struct.unpack('!Q',data[:header_len])[0]
data = data[header_len:]
- display.vvvv("%s: data received so far (expecting %d): %d" % (self._play_context.remote_addr,data_len,len(data)))
+ display.vvvv("data received so far (expecting %d): %d" % (data_len, len(data)), host=self._play_context.remote_addr)
while len(data) < data_len:
d = self.conn.recv(data_len - len(data))
if not d:
- display.vvvv("%s: received nothing, bailing out" % self._play_context.remote_addr)
+ display.vvvv("received nothing, bailing out", host=self._play_context.remote_addr)
return None
- display.vvvv("%s: received %d bytes" % (self._play_context.remote_addr, len(d)))
+ display.vvvv("received %d bytes" % (len(d)), host=self._play_context.remote_addr)
data += d
- display.vvvv("%s: received all of the data, returning" % self._play_context.remote_addr)
+ display.vvvv("received all of the data, returning", host=self._play_context.remote_addr)
return data
except socket.timeout:
raise AnsibleError("timed out while waiting to receive data")
@@ -132,7 +132,7 @@ class Connection(ConnectionBase):
daemon to exit if they don't match
'''
- display.vvvv("%s: sending request for validate_user" % self._play_context.remote_addr)
+ display.vvvv("sending request for validate_user", host=self._play_context.remote_addr)
data = dict(
mode='validate_user',
username=self._play_context.remote_user,
@@ -142,7 +142,7 @@ class Connection(ConnectionBase):
if self.send_data(data):
raise AnsibleError("Failed to send command to %s" % self._play_context.remote_addr)
- display.vvvv("%s: waiting for validate_user response" % self._play_context.remote_addr)
+ display.vvvv("waiting for validate_user response", host=self._play_context.remote_addr)
while True:
# we loop here while waiting for the response, because a
# long running command may cause us to receive keepalive packets
@@ -154,10 +154,10 @@ class Connection(ConnectionBase):
response = json.loads(response)
if "pong" in response:
# it's a keepalive, go back to waiting
- display.vvvv("%s: received a keepalive packet" % self._play_context.remote_addr)
+ display.vvvv("received a keepalive packet", host=self._play_context.remote_addr)
continue
else:
- display.vvvv("%s: received the validate_user response: %s" % (self._play_context.remote_addr, response))
+ display.vvvv("received the validate_user response: %s" % (response), host=self._play_context.remote_addr)
break
if response.get('failed'):
@@ -174,7 +174,7 @@ class Connection(ConnectionBase):
if in_data:
raise AnsibleError("Internal Error: this module does not support optimized module pipelining")
- display.vvv("EXEC COMMAND %s" % cmd)
+ display.vvv("EXEC COMMAND %s" % cmd, host=self._play_context.remote_addr)
data = dict(
mode='command',
@@ -197,10 +197,10 @@ class Connection(ConnectionBase):
response = json.loads(response)
if "pong" in response:
# it's a keepalive, go back to waiting
- display.vvvv("%s: received a keepalive packet" % self._play_context.remote_addr)
+ display.vvvv("received a keepalive packet", host=self._play_context.remote_addr)
continue
else:
- display.vvvv("%s: received the response" % self._play_context.remote_addr)
+ display.vvvv("received the response", host=self._play_context.remote_addr)
break
return (response.get('rc', None), response.get('stdout', ''), response.get('stderr', ''))
@@ -216,10 +216,10 @@ class Connection(ConnectionBase):
fd = file(in_path, 'rb')
fstat = os.stat(in_path)
try:
- display.vvv("PUT file is %d bytes" % fstat.st_size)
+ display.vvv("PUT file is %d bytes" % fstat.st_size, host=self._play_context.remote_addr)
last = False
while fd.tell() <= fstat.st_size and not last:
- display.vvvv("file position currently %ld, file size is %ld" % (fd.tell(), fstat.st_size))
+ display.vvvv("file position currently %ld, file size is %ld" % (fd.tell(), fstat.st_size), host=self._play_context.remote_addr)
data = fd.read(CHUNK_SIZE)
if fd.tell() >= fstat.st_size:
last = True
@@ -242,7 +242,7 @@ class Connection(ConnectionBase):
raise AnsibleError("failed to put the file in the requested location")
finally:
fd.close()
- display.vvvv("waiting for final response after PUT")
+ display.vvvv("waiting for final response after PUT", host=self._play_context.remote_addr)
response = self.recv_data()
if not response:
raise AnsibleError("Failed to get a response from %s" % self._play_context.remote_addr)
@@ -290,7 +290,7 @@ class Connection(ConnectionBase):
# point in the future or we may just have the put/fetch
# operations not send back a final response at all
response = self.recv_data()
- display.vvv("FETCH wrote %d bytes to %s" % (bytes, out_path))
+ display.vvv("FETCH wrote %d bytes to %s" % (bytes, out_path), host=self._play_context.remote_addr)
fh.close()
def close(self):
diff --git a/lib/ansible/plugins/connection/docker.py b/lib/ansible/plugins/connection/docker.py
index df6c870710..62404fb43c 100644
--- a/lib/ansible/plugins/connection/docker.py
+++ b/lib/ansible/plugins/connection/docker.py
@@ -170,7 +170,7 @@ class Connection(ConnectionBase):
super(Connection, self)._connect()
if not self._connected:
display.vvv(u"ESTABLISH DOCKER CONNECTION FOR USER: {0}".format(
- self.actual_user or '?', host=self._play_context.remote_addr)
+ self.actual_user or '?'), host=self._play_context.remote_addr
)
self._connected = True
diff --git a/lib/ansible/plugins/connection/local.py b/lib/ansible/plugins/connection/local.py
index 737a31971e..c66218b882 100644
--- a/lib/ansible/plugins/connection/local.py
+++ b/lib/ansible/plugins/connection/local.py
@@ -55,7 +55,7 @@ class Connection(ConnectionBase):
self._play_context.remote_user = getpass.getuser()
if not self._connected:
- display.vvv(u"ESTABLISH LOCAL CONNECTION FOR USER: {0}".format(self._play_context.remote_user, host=self._play_context.remote_addr))
+ display.vvv(u"ESTABLISH LOCAL CONNECTION FOR USER: {0}".format(self._play_context.remote_user), host=self._play_context.remote_addr)
self._connected = True
return self
@@ -68,7 +68,7 @@ class Connection(ConnectionBase):
executable = C.DEFAULT_EXECUTABLE.split()[0] if C.DEFAULT_EXECUTABLE else None
- display.vvv(u"{0} EXEC {1}".format(self._play_context.remote_addr, cmd))
+ display.vvv(u"EXEC {0}".format(cmd), host=self._play_context.remote_addr)
# FIXME: cwd= needs to be set to the basedir of the playbook
display.debug("opening command with Popen()")
@@ -122,7 +122,7 @@ class Connection(ConnectionBase):
super(Connection, self).put_file(in_path, out_path)
- display.vvv(u"{0} PUT {1} TO {2}".format(self._play_context.remote_addr, in_path, out_path))
+ display.vvv(u"PUT {0} TO {1}".format(in_path, out_path), host=self._play_context.remote_addr)
if not os.path.exists(to_bytes(in_path, errors='strict')):
raise AnsibleFileNotFound("file or module does not exist: {0}".format(to_str(in_path)))
try:
@@ -137,7 +137,7 @@ class Connection(ConnectionBase):
super(Connection, self).fetch_file(in_path, out_path)
- display.vvv(u"{0} FETCH {1} TO {2}".format(self._play_context.remote_addr, in_path, out_path))
+ display.vvv(u"FETCH {0} TO {1}".format(in_path, out_path), host=self._play_context.remote_addr)
self.put_file(in_path, out_path)
def close(self):
diff --git a/lib/ansible/plugins/connection/ssh.py b/lib/ansible/plugins/connection/ssh.py
index 56acd57afb..dcea2e5f1c 100644
--- a/lib/ansible/plugins/connection/ssh.py
+++ b/lib/ansible/plugins/connection/ssh.py
@@ -608,7 +608,7 @@ class Connection(ConnectionBase):
else:
msg = "ssh_retry: attempt: %d, caught exception(%s) from cmd (%s), pausing for %d seconds" % (attempt, e, cmd_summary, pause)
- display.vv(msg)
+ display.vv(msg, host=self.host)
time.sleep(pause)
continue
diff --git a/lib/ansible/plugins/filter/ipaddr.py b/lib/ansible/plugins/filter/ipaddr.py
index 432de6031b..bfcf32af05 100644
--- a/lib/ansible/plugins/filter/ipaddr.py
+++ b/lib/ansible/plugins/filter/ipaddr.py
@@ -670,6 +670,11 @@ def _need_netaddr(f_name, *args, **kwargs):
raise errors.AnsibleFilterError('The {0} filter requires python-netaddr be'
' installed on the ansible controller'.format(f_name))
+def ip4_hex(arg):
+ ''' Convert an IPv4 address to Hexadecimal notation '''
+ numbers = list(map(int, arg.split('.')))
+ return '{:02x}{:02x}{:02x}{:02x}'.format(*numbers)
+
# ---- Ansible filters ----
class FilterModule(object):
@@ -683,6 +688,7 @@ class FilterModule(object):
'ipsubnet': ipsubnet,
'nthhost': nthhost,
'slaac': slaac,
+ 'ip4_hex': ip4_hex,
# MAC / HW addresses
'hwaddr': hwaddr,
diff --git a/lib/ansible/plugins/shell/__init__.py b/lib/ansible/plugins/shell/__init__.py
index daa011e3da..c3e843f90b 100644
--- a/lib/ansible/plugins/shell/__init__.py
+++ b/lib/ansible/plugins/shell/__init__.py
@@ -94,6 +94,10 @@ class ShellBase(object):
cmd += '-r '
return cmd + "%s %s" % (path, self._SHELL_REDIRECT_ALLNULL)
+ def exists(self, path):
+ cmd = ['test', '-e', pipes.quote(path)]
+ return ' '.join(cmd)
+
def mkdtemp(self, basefile=None, system=False, mode=None):
if not basefile:
basefile = 'ansible-tmp-%s-%s' % (time.time(), random.randint(0, 2**48))
diff --git a/lib/ansible/plugins/shell/powershell.py b/lib/ansible/plugins/shell/powershell.py
index 569df09d44..d72361e431 100644
--- a/lib/ansible/plugins/shell/powershell.py
+++ b/lib/ansible/plugins/shell/powershell.py
@@ -94,6 +94,22 @@ class ShellModule(object):
script = 'Write-Host "%s"' % self._escape(user_home_path)
return self._encode_script(script)
+ def exists(self, path):
+ path = self._escape(self._unquote(path))
+ script = '''
+ If (Test-Path "%s")
+ {
+ $res = 0;
+ }
+ Else
+ {
+ $res = 1;
+ }
+ Write-Host "$res";
+ Exit $res;
+ ''' % path
+ return self._encode_script(script)
+
def checksum(self, path, *args, **kwargs):
path = self._escape(self._unquote(path))
script = '''
diff --git a/lib/ansible/utils/module_docs_fragments/eos.py b/lib/ansible/utils/module_docs_fragments/eos.py
index bd8d3f510e..6502710707 100644
--- a/lib/ansible/utils/module_docs_fragments/eos.py
+++ b/lib/ansible/utils/module_docs_fragments/eos.py
@@ -56,8 +56,8 @@ options:
before sending any commands. If not specified, the device will
attempt to excecute all commands in non-priviledged mode.
required: false
- default: false
- choices: BOOLEANS
+ default: no
+ choices: ['yes', 'no']
auth_pass:
description:
- Specifies the password to use if required to enter privileged mode
@@ -78,8 +78,8 @@ options:
I(transport) argument is configured as eapi. If the transport
argument is not eapi, this value is ignored
required: false
- default: true
- choices: BOOLEANS
+ default: yes
+ choices: ['yes', 'no']
provider:
description:
- Convience method that allows all M(eos) arguments to be passed as
diff --git a/lib/ansible/utils/module_docs_fragments/ios.py b/lib/ansible/utils/module_docs_fragments/ios.py
index 66ba28ad02..4b6e53fc0c 100644
--- a/lib/ansible/utils/module_docs_fragments/ios.py
+++ b/lib/ansible/utils/module_docs_fragments/ios.py
@@ -54,8 +54,8 @@ options:
before sending any commands. If not specified, the device will
attempt to excecute all commands in non-priviledged mode.
required: false
- default: false
- choices: BOOLEANS
+ default: no
+ choices: ['yes', 'no']
auth_pass:
description:
- Specifies the password to use if required to enter privileged mode
diff --git a/lib/ansible/utils/module_docs_fragments/nxos.py b/lib/ansible/utils/module_docs_fragments/nxos.py
index 26312155c4..d2d5343b39 100644
--- a/lib/ansible/utils/module_docs_fragments/nxos.py
+++ b/lib/ansible/utils/module_docs_fragments/nxos.py
@@ -63,8 +63,8 @@ options:
I(transport) argument is configured as nxapi. If the transport
argument is not nxapi, this value is ignored
required: false
- default: false
- choices: BOOLEANS
+ default: no
+ choices: ['yes', 'no']
provider:
description:
- Convience method that allows all M(nxos) arguments to be passed as
diff --git a/lib/ansible/utils/module_docs_fragments/openswitch.py b/lib/ansible/utils/module_docs_fragments/openswitch.py
index 3b3dbcaecc..7a223ce761 100644
--- a/lib/ansible/utils/module_docs_fragments/openswitch.py
+++ b/lib/ansible/utils/module_docs_fragments/openswitch.py
@@ -68,8 +68,8 @@ options:
I(transport) argument is configured as rest. If the transport
argument is not rest, this value is ignored
required: false
- default: true
- choices: BOOLEANS
+ default: yes
+ choices: ['yes', 'no']
provider:
description:
- Convience method that allows all M(openswitch) arguments to be passed as
diff --git a/test/integration/Makefile b/test/integration/Makefile
index f12cc03a0f..39f4fd3872 100644
--- a/test/integration/Makefile
+++ b/test/integration/Makefile
@@ -227,6 +227,11 @@ cloudstack:
RC=$$? ; \
exit $$RC;
+cloudflare: $(CREDENTIALS_FILE)
+ ansible-playbook cloudflare.yml -i $(INVENTORY) -e @$(VARS_FILE) -e @$(CREDENTIALS_FILE) -e "resource_prefix=$(CLOUD_RESOURCE_PREFIX)" -v $(TEST_FLAGS) ; \
+ RC=$$? ; \
+ exit $$RC;
+
$(CONSUL_RUNNING):
consul:
diff --git a/test/integration/cloudflare.yml b/test/integration/cloudflare.yml
new file mode 100644
index 0000000000..7b3891e717
--- /dev/null
+++ b/test/integration/cloudflare.yml
@@ -0,0 +1,8 @@
+---
+- hosts: localhost
+ connection: local
+ gather_facts: no
+ tags:
+ - cloudflare
+ roles:
+ - { role: test_cloudflare_dns, tags: test_cloudflare_dns }
diff --git a/test/integration/credentials.template b/test/integration/credentials.template
index fb052a42c2..bbf7c9ba6e 100644
--- a/test/integration/credentials.template
+++ b/test/integration/credentials.template
@@ -19,3 +19,8 @@ azure_cert_path: "{{ lookup('env', 'AZURE_CERT_PATH') }}"
# GITHUB SSH private key - a path to a SSH private key for use with github.com
github_ssh_private_key: "{{ lookup('env','HOME') }}/.ssh/id_rsa"
+
+# Cloudflare Credentials
+cloudflare_api_token:
+cloudflare_email:
+cloudflare_zone:
diff --git a/test/integration/roles/test_cloudflare_dns/defaults/main.yml b/test/integration/roles/test_cloudflare_dns/defaults/main.yml
new file mode 100644
index 0000000000..bdd939a0fc
--- /dev/null
+++ b/test/integration/roles/test_cloudflare_dns/defaults/main.yml
@@ -0,0 +1,2 @@
+---
+cloudflare_dns_record: "{{ resource_prefix|lower }}"
diff --git a/test/integration/roles/test_cloudflare_dns/tasks/a_record.yml b/test/integration/roles/test_cloudflare_dns/tasks/a_record.yml
new file mode 100644
index 0000000000..c3f0d14312
--- /dev/null
+++ b/test/integration/roles/test_cloudflare_dns/tasks/a_record.yml
@@ -0,0 +1,180 @@
+---
+######## A record tests #################
+
+- name: "Test: A record creation"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: A
+ value: 127.0.0.1
+ ttl: 150
+ register: cloudflare_dns
+
+- name: "Validate: A record creation"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - cloudflare_dns|changed
+ - cloudflare_dns.result.record.content == '127.0.0.1'
+ - cloudflare_dns.result.record.ttl == 150
+ - cloudflare_dns.result.record.type == 'A'
+ - cloudflare_dns.result.record.name == "{{ cloudflare_dns_record }}.{{ cloudflare_zone }}"
+ - cloudflare_dns.result.record.zone_name == "{{ cloudflare_zone }}"
+
+- name: "Test: A record idempotency"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: A
+ value: 127.0.0.1
+ ttl: 150
+ register: cloudflare_dns
+
+- name: "Validate: A record idempotency"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - not cloudflare_dns|changed
+
+- name: "Test: A record update"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: A
+ value: 127.0.0.1
+ ttl: 300
+ register: cloudflare_dns
+
+- name: "Validate: A record update"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - cloudflare_dns|changed
+ - cloudflare_dns.result.record.ttl == 300
+
+- name: "Test: A record duplicate (create new record)"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: A
+ value: 127.0.1.1
+ ttl: 150
+ register: cloudflare_dns
+
+- name: "Validate: A record duplicate (create new record)"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - cloudflare_dns|changed
+ - cloudflare_dns.result.record.content == '127.0.1.1'
+ - cloudflare_dns.result.record.ttl == 150
+ - cloudflare_dns.result.record.type == 'A'
+ - cloudflare_dns.result.record.name == "{{ cloudflare_dns_record }}.{{ cloudflare_zone }}"
+ - cloudflare_dns.result.record.zone_name == "{{ cloudflare_zone }}"
+
+- name: "Test: A record duplicate (old record present)"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: A
+ value: 127.0.0.1
+ ttl: 300
+ register: cloudflare_dns
+
+- name: "Validate: A record duplicate (old record present)"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - not cloudflare_dns|changed
+ - cloudflare_dns.result.record.content == '127.0.0.1'
+ - cloudflare_dns.result.record.ttl == 300
+ - cloudflare_dns.result.record.type == 'A'
+ - cloudflare_dns.result.record.name == "{{ cloudflare_dns_record }}.{{ cloudflare_zone }}"
+ - cloudflare_dns.result.record.zone_name == "{{ cloudflare_zone }}"
+
+- name: "Test: A record duplicate (make new record solo)"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: A
+ value: 127.0.1.1
+ ttl: 150
+ solo: true
+ register: cloudflare_dns
+
+- name: "Validate: A record duplicate (make new record solo)"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - cloudflare_dns|changed
+ - cloudflare_dns.result.record.content == '127.0.1.1'
+ - cloudflare_dns.result.record.ttl == 150
+ - cloudflare_dns.result.record.type == 'A'
+ - cloudflare_dns.result.record.name == "{{ cloudflare_dns_record }}.{{ cloudflare_zone }}"
+ - cloudflare_dns.result.record.zone_name == "{{ cloudflare_zone }}"
+
+- name: "Test: A record duplicate (old record absent)"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: A
+ value: 127.0.0.1
+ ttl: 300
+ state: absent
+ register: cloudflare_dns
+
+- name: "Validate: A record duplicate (old record absent)"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - not cloudflare_dns|changed
+
+- name: "Test: A record deletion"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: A
+ value: 127.0.1.1
+ ttl: 150
+ state: absent
+ register: cloudflare_dns
+
+- name: "Validate: A record deletion"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - cloudflare_dns|changed
+
+- name: "Test: A record deletion succeeded"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: A
+ value: 127.0.1.1
+ ttl: 150
+ state: absent
+ register: cloudflare_dns
+
+- name: "Validate: A record deletion succeeded"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - not cloudflare_dns|changed
diff --git a/test/integration/roles/test_cloudflare_dns/tasks/aaaa_record.yml b/test/integration/roles/test_cloudflare_dns/tasks/aaaa_record.yml
new file mode 100644
index 0000000000..6a8bdf022e
--- /dev/null
+++ b/test/integration/roles/test_cloudflare_dns/tasks/aaaa_record.yml
@@ -0,0 +1,180 @@
+---
+######## AAAA record tests #################
+
+- name: "Test: AAAA record creation"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: AAAA
+ value: "::1"
+ ttl: 150
+ register: cloudflare_dns
+
+- name: "Validate: AAAA record creation"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - cloudflare_dns|changed
+ - cloudflare_dns.result.record.content == '::1'
+ - cloudflare_dns.result.record.ttl == 150
+ - cloudflare_dns.result.record.type == 'AAAA'
+ - cloudflare_dns.result.record.name == "{{ cloudflare_dns_record }}.{{ cloudflare_zone }}"
+ - cloudflare_dns.result.record.zone_name == "{{ cloudflare_zone }}"
+
+- name: "Test: AAAA record idempotency"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: AAAA
+ value: "::1"
+ ttl: 150
+ register: cloudflare_dns
+
+- name: "Validate: AAAA record idempotency"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - not cloudflare_dns|changed
+
+- name: "Test: AAAA record update"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: AAAA
+ value: "::1"
+ ttl: 300
+ register: cloudflare_dns
+
+- name: "Validate: AAAA record update"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - cloudflare_dns|changed
+ - cloudflare_dns.result.record.ttl == 300
+
+- name: "Test: AAAA record duplicate (create new record)"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: AAAA
+ value: "::2"
+ ttl: 150
+ register: cloudflare_dns
+
+- name: "Validate: AAAA record duplicate (create new record)"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - cloudflare_dns|changed
+ - cloudflare_dns.result.record.content == '::2'
+ - cloudflare_dns.result.record.ttl == 150
+ - cloudflare_dns.result.record.type == 'AAAA'
+ - cloudflare_dns.result.record.name == "{{ cloudflare_dns_record }}.{{ cloudflare_zone }}"
+ - cloudflare_dns.result.record.zone_name == "{{ cloudflare_zone }}"
+
+- name: "Test: AAAA record duplicate (old record present)"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: AAAA
+ value: "::1"
+ ttl: 300
+ register: cloudflare_dns
+
+- name: "Validate: AAAA record duplicate (old record present)"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - not cloudflare_dns|changed
+ - cloudflare_dns.result.record.content == '::1'
+ - cloudflare_dns.result.record.ttl == 300
+ - cloudflare_dns.result.record.type == 'AAAA'
+ - cloudflare_dns.result.record.name == "{{ cloudflare_dns_record }}.{{ cloudflare_zone }}"
+ - cloudflare_dns.result.record.zone_name == "{{ cloudflare_zone }}"
+
+- name: "Test: AAAA record duplicate (make new record solo)"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: AAAA
+ value: "::2"
+ ttl: 150
+ solo: true
+ register: cloudflare_dns
+
+- name: "Validate: AAAA record duplicate (make new record solo)"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - cloudflare_dns|changed
+ - cloudflare_dns.result.record.content == '::2'
+ - cloudflare_dns.result.record.ttl == 150
+ - cloudflare_dns.result.record.type == 'AAAA'
+ - cloudflare_dns.result.record.name == "{{ cloudflare_dns_record }}.{{ cloudflare_zone }}"
+ - cloudflare_dns.result.record.zone_name == "{{ cloudflare_zone }}"
+
+- name: "Test: AAAA record duplicate (old record absent)"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: AAAA
+ value: "::1"
+ ttl: 300
+ state: absent
+ register: cloudflare_dns
+
+- name: "Validate: AAAA record duplicate (old record absent)"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - not cloudflare_dns|changed
+
+- name: "Test: AAAA record deletion"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: AAAA
+ value: "::2"
+ ttl: 150
+ state: absent
+ register: cloudflare_dns
+
+- name: "Validate: AAAA record deletion"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - cloudflare_dns|changed
+
+- name: "Test: AAAA record deletion succeeded"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: AAAA
+ value: "::2"
+ ttl: 150
+ state: absent
+ register: cloudflare_dns
+
+- name: "Validate: AAAA record deletion succeeded"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - not cloudflare_dns|changed
diff --git a/test/integration/roles/test_cloudflare_dns/tasks/cname_record.yml b/test/integration/roles/test_cloudflare_dns/tasks/cname_record.yml
new file mode 100644
index 0000000000..a73da692d1
--- /dev/null
+++ b/test/integration/roles/test_cloudflare_dns/tasks/cname_record.yml
@@ -0,0 +1,139 @@
+---
+######## CNAME record tests #################
+
+# the '.' at the end of the value is intentional
+# it must be verified that the '.' will be stripped
+- name: "Test: CNAME record creation"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: CNAME
+ value: "srv1.{{ cloudflare_zone }}."
+ ttl: 150
+ register: cloudflare_dns
+
+- name: "Validate: CNAME record creation"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - cloudflare_dns|changed
+ - cloudflare_dns.result.record.content == "srv1.{{ cloudflare_zone }}"
+ - cloudflare_dns.result.record.ttl == 150
+ - cloudflare_dns.result.record.type == 'CNAME'
+ - cloudflare_dns.result.record.name == "{{ cloudflare_dns_record }}.{{ cloudflare_zone }}"
+ - cloudflare_dns.result.record.zone_name == "{{ cloudflare_zone }}"
+
+- name: "Test: CNAME record idempotency"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: CNAME
+ value: "srv1.{{ cloudflare_zone }}."
+ ttl: 150
+ register: cloudflare_dns
+
+- name: "Validate: CNAME record idempotency"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - not cloudflare_dns|changed
+
+- name: "Test: CNAME record update"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: CNAME
+ value: "srv2.{{ cloudflare_zone }}"
+ ttl: 300
+ register: cloudflare_dns
+
+- name: "Validate: CNAME record update"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - cloudflare_dns|changed
+ - cloudflare_dns.result.record.content == "srv2.{{ cloudflare_zone }}"
+ - cloudflare_dns.result.record.ttl == 300
+
+- name: "Test: CNAME record duplicate (make new record solo)"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: CNAME
+ value: "srv3.{{ cloudflare_zone }}"
+ ttl: 600
+ solo: true
+ register: cloudflare_dns
+
+- name: "Validate: CNAME record duplicate (make new record solo)"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - cloudflare_dns|changed
+ - cloudflare_dns.result.record.content == "srv3.{{ cloudflare_zone }}"
+ - cloudflare_dns.result.record.ttl == 600
+ - cloudflare_dns.result.record.type == 'CNAME'
+ - cloudflare_dns.result.record.name == "{{ cloudflare_dns_record }}.{{ cloudflare_zone }}"
+ - cloudflare_dns.result.record.zone_name == "{{ cloudflare_zone }}"
+
+- name: "Test: CNAME record duplicate (old record absent)"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: CNAME
+ value: "srv2.{{ cloudflare_zone }}"
+ ttl: 300
+ state: absent
+ register: cloudflare_dns
+
+- name: "Validate: CNAME record duplicate (old record absent)"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - not cloudflare_dns|changed
+
+- name: "Test: CNAME record deletion"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: CNAME
+ value: "srv3.{{ cloudflare_zone }}"
+ ttl: 600
+ state: absent
+ register: cloudflare_dns
+
+- name: "Validate: CNAME record deletion"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - cloudflare_dns|changed
+
+- name: "Test: CNAME record deletion succeeded"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: CNAME
+ value: "srv3.{{ cloudflare_zone }}"
+ ttl: 600
+ state: absent
+ register: cloudflare_dns
+
+- name: "Validate: CNAME record deletion succeeded"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - not cloudflare_dns|changed
diff --git a/test/integration/roles/test_cloudflare_dns/tasks/main.yml b/test/integration/roles/test_cloudflare_dns/tasks/main.yml
new file mode 100644
index 0000000000..7d349d9598
--- /dev/null
+++ b/test/integration/roles/test_cloudflare_dns/tasks/main.yml
@@ -0,0 +1,64 @@
+---
+- name: "Test: no args"
+ cloudflare_dns:
+ ignore_errors: true
+ register: cloudflare_dns
+
+- name: "Validate: no args"
+ assert:
+ that:
+ - cloudflare_dns|failed
+ - "cloudflare_dns.msg.find('missing required arguments: ') != -1"
+
+- name: "Test: only credentials"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ ignore_errors: true
+ register: cloudflare_dns
+
+- name: "Validate: only credentials"
+ assert:
+ that:
+ - cloudflare_dns|failed
+ - "cloudflare_dns.msg.find('missing required arguments: ') != -1"
+
+- name: "Test: credentials and zone"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ ignore_errors: true
+ register: cloudflare_dns
+
+- name: "Validate: credentials and zone"
+ assert:
+ that:
+ - cloudflare_dns|failed
+ - "cloudflare_dns.msg.find('but the following are missing: ') != -1"
+
+- name: "Test: credentials, zone and type"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ type: TXT
+ ignore_errors: true
+ register: cloudflare_dns
+
+- name: "Validate: credentials, zone and type"
+ assert:
+ that:
+ - cloudflare_dns|failed
+ - "cloudflare_dns.msg.find('but the following are missing: ') != -1"
+
+######## record tests #################
+
+- include: a_record.yml
+- include: aaaa_record.yml
+- include: cname_record.yml
+- include: txt_record.yml
+- include: ns_record.yml
+- include: spf_record.yml
+- include: mx_record.yml
+- include: srv_record.yml
diff --git a/test/integration/roles/test_cloudflare_dns/tasks/mx_record.yml b/test/integration/roles/test_cloudflare_dns/tasks/mx_record.yml
new file mode 100644
index 0000000000..dcb12e64d9
--- /dev/null
+++ b/test/integration/roles/test_cloudflare_dns/tasks/mx_record.yml
@@ -0,0 +1,194 @@
+---
+######## MX record tests #################
+
+- name: "Test: MX record creation"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: MX
+ value: "mx1-{{ cloudflare_dns_record }}.{{ cloudflare_zone }}."
+ ttl: 150
+ priority: 20
+ register: cloudflare_dns
+
+- name: "Validate: MX record creation"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - cloudflare_dns|changed
+ - cloudflare_dns.result.record.content == 'mx1-{{ cloudflare_dns_record }}.{{ cloudflare_zone }}'
+ - cloudflare_dns.result.record.ttl == 150
+ - cloudflare_dns.result.record.priority == 20
+ - cloudflare_dns.result.record.type == 'MX'
+ - cloudflare_dns.result.record.name == "{{ cloudflare_dns_record }}.{{ cloudflare_zone }}"
+ - cloudflare_dns.result.record.zone_name == "{{ cloudflare_zone }}"
+
+- name: "Test: MX record idempotency"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: MX
+ value: "mx1-{{ cloudflare_dns_record }}.{{ cloudflare_zone }}."
+ ttl: 150
+ priority: 20
+ register: cloudflare_dns
+
+- name: "Validate: MX record idempotency"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - not cloudflare_dns|changed
+
+- name: "Test: MX record update"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: MX
+ value: "mx1-{{ cloudflare_dns_record }}.{{ cloudflare_zone }}"
+ ttl: 300
+ priority: 10
+ register: cloudflare_dns
+
+- name: "Validate: MX record update"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - cloudflare_dns|changed
+ - cloudflare_dns.result.record.ttl == 300
+ - cloudflare_dns.result.record.priority == 10
+
+- name: "Test: MX record duplicate (create new record)"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: MX
+ value: "mx2-{{ cloudflare_dns_record }}.{{ cloudflare_zone }}"
+ ttl: 150
+ priority: 30
+ register: cloudflare_dns
+
+- name: "Validate: MX record duplicate (create new record)"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - cloudflare_dns|changed
+ - cloudflare_dns.result.record.content == 'mx2-{{ cloudflare_dns_record }}.{{ cloudflare_zone }}'
+ - cloudflare_dns.result.record.ttl == 150
+ - cloudflare_dns.result.record.priority == 30
+ - cloudflare_dns.result.record.type == 'MX'
+ - cloudflare_dns.result.record.name == "{{ cloudflare_dns_record }}.{{ cloudflare_zone }}"
+ - cloudflare_dns.result.record.zone_name == "{{ cloudflare_zone }}"
+
+- name: "Test: MX record duplicate (old record present)"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: MX
+ value: "mx1-{{ cloudflare_dns_record }}.{{ cloudflare_zone }}"
+ ttl: 300
+ priority: 10
+ register: cloudflare_dns
+
+- name: "Validate: MX record duplicate (old record present)"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - not cloudflare_dns|changed
+ - cloudflare_dns.result.record.content == 'mx1-{{ cloudflare_dns_record }}.{{ cloudflare_zone }}'
+ - cloudflare_dns.result.record.ttl == 300
+ - cloudflare_dns.result.record.priority == 10
+ - cloudflare_dns.result.record.type == 'MX'
+ - cloudflare_dns.result.record.name == "{{ cloudflare_dns_record }}.{{ cloudflare_zone }}"
+ - cloudflare_dns.result.record.zone_name == "{{ cloudflare_zone }}"
+
+- name: "Test: MX record duplicate (make new record solo)"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: MX
+ value: "mx2-{{ cloudflare_dns_record }}.{{ cloudflare_zone }}"
+ ttl: 150
+ priority: 30
+ solo: true
+ register: cloudflare_dns
+
+- name: "Validate: MX record duplicate (make new record solo)"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - cloudflare_dns|changed
+ - cloudflare_dns.result.record.content == 'mx2-{{ cloudflare_dns_record }}.{{ cloudflare_zone }}'
+ - cloudflare_dns.result.record.ttl == 150
+ - cloudflare_dns.result.record.priority == 30
+ - cloudflare_dns.result.record.type == 'MX'
+ - cloudflare_dns.result.record.name == "{{ cloudflare_dns_record }}.{{ cloudflare_zone }}"
+ - cloudflare_dns.result.record.zone_name == "{{ cloudflare_zone }}"
+
+- name: "Test: MX record duplicate (old record absent)"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: MX
+ value: "mx1-{{ cloudflare_dns_record }}.{{ cloudflare_zone }}"
+ ttl: 300
+ priority: 10
+ state: absent
+ register: cloudflare_dns
+
+- name: "Validate: MX record duplicate (old record absent)"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - not cloudflare_dns|changed
+
+- name: "Test: MX record deletion"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: MX
+ value: "mx2-{{ cloudflare_dns_record }}.{{ cloudflare_zone }}"
+ ttl: 150
+ priority: 30
+ state: absent
+ register: cloudflare_dns
+
+- name: "Validate: MX record deletion"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - cloudflare_dns|changed
+
+- name: "Test: MX record deletion succeeded"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: MX
+ value: "mx2-{{ cloudflare_dns_record }}.{{ cloudflare_zone }}"
+ ttl: 150
+ priority: 30
+ state: absent
+ register: cloudflare_dns
+
+- name: "Validate: MX record deletion succeeded"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - not cloudflare_dns|changed
diff --git a/test/integration/roles/test_cloudflare_dns/tasks/ns_record.yml b/test/integration/roles/test_cloudflare_dns/tasks/ns_record.yml
new file mode 100644
index 0000000000..aa00116bb7
--- /dev/null
+++ b/test/integration/roles/test_cloudflare_dns/tasks/ns_record.yml
@@ -0,0 +1,182 @@
+---
+######## NS record tests #################
+
+# the '.' at the end of the value is intentional
+# it must be verified that the '.' will be stripped
+- name: "Test: NS record creation"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: NS
+ value: an.si.ble.
+ ttl: 150
+ register: cloudflare_dns
+
+- name: "Validate: NS record creation"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - cloudflare_dns|changed
+ - cloudflare_dns.result.record.content == 'an.si.ble'
+ - cloudflare_dns.result.record.ttl == 150
+ - cloudflare_dns.result.record.type == 'NS'
+ - cloudflare_dns.result.record.name == "{{ cloudflare_dns_record }}.{{ cloudflare_zone }}"
+ - cloudflare_dns.result.record.zone_name == "{{ cloudflare_zone }}"
+
+- name: "Test: NS record idempotency"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: NS
+ value: an.si.ble.
+ ttl: 150
+ register: cloudflare_dns
+
+- name: "Validate: NS record idempotency"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - not cloudflare_dns|changed
+
+- name: "Test: NS record update"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: NS
+ value: an.si.ble
+ ttl: 300
+ register: cloudflare_dns
+
+- name: "Validate: NS record update"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - cloudflare_dns|changed
+ - cloudflare_dns.result.record.ttl == 300
+
+- name: "Test: NS record duplicate (create new record)"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: NS
+ value: ble.si.an
+ ttl: 150
+ register: cloudflare_dns
+
+- name: "Validate: NS record duplicate (create new record)"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - cloudflare_dns|changed
+ - cloudflare_dns.result.record.content == 'ble.si.an'
+ - cloudflare_dns.result.record.ttl == 150
+ - cloudflare_dns.result.record.type == 'NS'
+ - cloudflare_dns.result.record.name == "{{ cloudflare_dns_record }}.{{ cloudflare_zone }}"
+ - cloudflare_dns.result.record.zone_name == "{{ cloudflare_zone }}"
+
+- name: "Test: NS record duplicate (old record present)"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: NS
+ value: an.si.ble
+ ttl: 300
+ register: cloudflare_dns
+
+- name: "Validate: NS record duplicate (old record present)"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - not cloudflare_dns|changed
+ - cloudflare_dns.result.record.content == 'an.si.ble'
+ - cloudflare_dns.result.record.ttl == 300
+ - cloudflare_dns.result.record.type == 'NS'
+ - cloudflare_dns.result.record.name == "{{ cloudflare_dns_record }}.{{ cloudflare_zone }}"
+ - cloudflare_dns.result.record.zone_name == "{{ cloudflare_zone }}"
+
+- name: "Test: NS record duplicate (make new record solo)"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: NS
+ value: ble.si.an
+ ttl: 150
+ solo: true
+ register: cloudflare_dns
+
+- name: "Validate: NS record duplicate (make new record solo)"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - cloudflare_dns|changed
+ - cloudflare_dns.result.record.content == 'ble.si.an'
+ - cloudflare_dns.result.record.ttl == 150
+ - cloudflare_dns.result.record.type == 'NS'
+ - cloudflare_dns.result.record.name == "{{ cloudflare_dns_record }}.{{ cloudflare_zone }}"
+ - cloudflare_dns.result.record.zone_name == "{{ cloudflare_zone }}"
+
+- name: "Test: NS record duplicate (old record absent)"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: NS
+ value: an.si.ble
+ ttl: 300
+ state: absent
+ register: cloudflare_dns
+
+- name: "Validate: NS record duplicate (old record absent)"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - not cloudflare_dns|changed
+
+- name: "Test: NS record deletion"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: NS
+ value: ble.si.an
+ ttl: 150
+ state: absent
+ register: cloudflare_dns
+
+- name: "Validate: NS record deletion"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - cloudflare_dns|changed
+
+- name: "Test: NS record deletion succeeded"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: NS
+ value: ble.si.an
+ ttl: 150
+ state: absent
+ register: cloudflare_dns
+
+- name: "Validate: NS record deletion succeeded"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - not cloudflare_dns|changed
diff --git a/test/integration/roles/test_cloudflare_dns/tasks/spf_record.yml b/test/integration/roles/test_cloudflare_dns/tasks/spf_record.yml
new file mode 100644
index 0000000000..6e10dfccd3
--- /dev/null
+++ b/test/integration/roles/test_cloudflare_dns/tasks/spf_record.yml
@@ -0,0 +1,184 @@
+---
+######## SPF record tests #################
+
+- set_fact:
+ # values breaking the api: ,<>:
+ txt_teststring: 'v=spf1 abc123 !@#$%^&*()_+=-;./{}?\|'
+
+- name: "Test: SPF record creation"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: SPF
+ value: "{{ txt_teststring }}"
+ ttl: 150
+ register: cloudflare_dns
+
+- name: "Validate: SPF record creation"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - cloudflare_dns|changed
+ - cloudflare_dns.result.record.content == "{{ txt_teststring }}"
+ - cloudflare_dns.result.record.ttl == 150
+ - cloudflare_dns.result.record.type == 'SPF'
+ - cloudflare_dns.result.record.name == "{{ cloudflare_dns_record }}.{{ cloudflare_zone }}"
+ - cloudflare_dns.result.record.zone_name == "{{ cloudflare_zone }}"
+
+- name: "Test: SPF record idempotency"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: SPF
+ value: "{{ txt_teststring }}"
+ ttl: 150
+ register: cloudflare_dns
+
+- name: "Validate: SPF record idempotency"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - not cloudflare_dns|changed
+
+- name: "Test: SPF record update"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: SPF
+ value: "{{ txt_teststring }}"
+ ttl: 300
+ register: cloudflare_dns
+
+- name: "Validate: SPF record update"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - cloudflare_dns|changed
+ - cloudflare_dns.result.record.ttl == 300
+
+- name: "Test: SPF record duplicate (create new record)"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: SPF
+ value: 'v=spf1 teststring'
+ ttl: 150
+ register: cloudflare_dns
+
+- name: "Validate: SPF record duplicate (create new record)"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - cloudflare_dns|changed
+ - cloudflare_dns.result.record.content == 'v=spf1 teststring'
+ - cloudflare_dns.result.record.ttl == 150
+ - cloudflare_dns.result.record.type == 'SPF'
+ - cloudflare_dns.result.record.name == "{{ cloudflare_dns_record }}.{{ cloudflare_zone }}"
+ - cloudflare_dns.result.record.zone_name == "{{ cloudflare_zone }}"
+
+- name: "Test: SPF record duplicate (old record present)"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: SPF
+ value: "{{ txt_teststring }}"
+ ttl: 300
+ register: cloudflare_dns
+
+- name: "Validate: SPF record duplicate (old record present)"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - not cloudflare_dns|changed
+ - cloudflare_dns.result.record.content == "{{ txt_teststring }}"
+ - cloudflare_dns.result.record.ttl == 300
+ - cloudflare_dns.result.record.type == 'SPF'
+ - cloudflare_dns.result.record.name == "{{ cloudflare_dns_record }}.{{ cloudflare_zone }}"
+ - cloudflare_dns.result.record.zone_name == "{{ cloudflare_zone }}"
+
+- name: "Test: SPF record duplicate (make new record solo)"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: SPF
+ value: 'v=spf1 teststring'
+ ttl: 150
+ solo: true
+ register: cloudflare_dns
+
+- name: "Validate: SPF record duplicate (make new record solo)"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - cloudflare_dns|changed
+ - cloudflare_dns.result.record.content == 'v=spf1 teststring'
+ - cloudflare_dns.result.record.ttl == 150
+ - cloudflare_dns.result.record.type == 'SPF'
+ - cloudflare_dns.result.record.name == "{{ cloudflare_dns_record }}.{{ cloudflare_zone }}"
+ - cloudflare_dns.result.record.zone_name == "{{ cloudflare_zone }}"
+
+- name: "Test: SPF record duplicate (old record absent)"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: SPF
+ value: "{{ txt_teststring }}"
+ ttl: 300
+ state: absent
+ register: cloudflare_dns
+
+- name: "Validate: SPF record duplicate (old record absent)"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - not cloudflare_dns|changed
+
+- name: "Test: SPF record deletion"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: SPF
+ value: 'v=spf1 teststring'
+ ttl: 150
+ state: absent
+ register: cloudflare_dns
+
+- name: "Validate: SPF record deletion"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - cloudflare_dns|changed
+
+- name: "Test: SPF record deletion succeeded"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: SPF
+ value: 'v=spf1 teststring'
+ ttl: 150
+ state: absent
+ register: cloudflare_dns
+
+- name: "Validate: SPF record deletion succeeded"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - not cloudflare_dns|changed
diff --git a/test/integration/roles/test_cloudflare_dns/tasks/srv_record.yml b/test/integration/roles/test_cloudflare_dns/tasks/srv_record.yml
new file mode 100644
index 0000000000..de19446084
--- /dev/null
+++ b/test/integration/roles/test_cloudflare_dns/tasks/srv_record.yml
@@ -0,0 +1,266 @@
+---
+######## SRV record tests #################
+
+- name: "Test: SRV record creation"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: SRV
+ value: "srv1.{{ cloudflare_dns_record }}.{{ cloudflare_zone }}."
+ ttl: 150
+ priority: 20
+ service: srv1
+ proto: tcp
+ port: 3500
+ weight: 5
+ register: cloudflare_dns
+
+- name: "Validate: SRV record creation"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - cloudflare_dns|changed
+ - cloudflare_dns.result.record.content == '5\t3500\tsrv1.{{ cloudflare_dns_record }}.{{ cloudflare_zone }}'
+ - cloudflare_dns.result.record.ttl == 150
+ - cloudflare_dns.result.record.data.target == 'srv1.{{ cloudflare_dns_record }}.{{ cloudflare_zone }}'
+ - cloudflare_dns.result.record.data.port == 3500
+ - cloudflare_dns.result.record.data.weight == 5
+ - cloudflare_dns.result.record.data.priority == 20
+ - cloudflare_dns.result.record.data.name == "{{ cloudflare_dns_record }}"
+ - cloudflare_dns.result.record.data.proto == '_tcp'
+ - cloudflare_dns.result.record.data.service == '_srv1'
+ - cloudflare_dns.result.record.type == 'SRV'
+ - cloudflare_dns.result.record.name == "_srv1._tcp.{{ cloudflare_dns_record }}.{{ cloudflare_zone }}"
+ - cloudflare_dns.result.record.zone_name == "{{ cloudflare_zone }}"
+
+- name: "Test: SRV record idempotency"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: SRV
+ value: "srv1.{{ cloudflare_dns_record }}.{{ cloudflare_zone }}."
+ ttl: 150
+ priority: 20
+ service: srv1
+ proto: tcp
+ port: 3500
+ weight: 5
+ register: cloudflare_dns
+
+- name: "Validate: SRV record idempotency"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - not cloudflare_dns|changed
+
+# changing the following attributes creates a new record:
+# weight
+# port
+# value
+# service
+# proto
+- name: "Test: SRV record update"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: SRV
+ value: "srv1.{{ cloudflare_dns_record }}.{{ cloudflare_zone }}"
+ ttl: 300
+ priority: 10
+ service: srv1
+ proto: tcp
+ port: 3500
+ weight: 5
+ register: cloudflare_dns
+
+- name: "Validate: SRV record update"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - cloudflare_dns|changed
+ - cloudflare_dns.result.record.ttl == 300
+ - cloudflare_dns.result.record.data.target == 'srv1.{{ cloudflare_dns_record }}.{{ cloudflare_zone }}'
+ - cloudflare_dns.result.record.data.port == 3500
+ - cloudflare_dns.result.record.data.weight == 5
+ - cloudflare_dns.result.record.data.priority == 10
+ - cloudflare_dns.result.record.data.name == "{{ cloudflare_dns_record }}"
+ - cloudflare_dns.result.record.data.proto == '_tcp'
+ - cloudflare_dns.result.record.data.service == '_srv1'
+
+- name: "Test: SRV record duplicate (create new record)"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: SRV
+ value: "srv2.{{ cloudflare_dns_record }}.{{ cloudflare_zone }}"
+ ttl: 150
+ priority: 30
+ service: srv1
+ proto: tcp
+ port: 9999
+ weight: 19
+ register: cloudflare_dns
+
+- name: "Validate: SRV record duplicate (create new record)"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - cloudflare_dns|changed
+ - cloudflare_dns.result.record.content == '19\t9999\tsrv2.{{ cloudflare_dns_record }}.{{ cloudflare_zone }}'
+ - cloudflare_dns.result.record.ttl == 150
+ - cloudflare_dns.result.record.data.target == 'srv2.{{ cloudflare_dns_record }}.{{ cloudflare_zone }}'
+ - cloudflare_dns.result.record.data.port == 9999
+ - cloudflare_dns.result.record.data.weight == 19
+ - cloudflare_dns.result.record.data.priority == 30
+ - cloudflare_dns.result.record.data.name == "{{ cloudflare_dns_record }}"
+ - cloudflare_dns.result.record.data.proto == '_tcp'
+ - cloudflare_dns.result.record.data.service == '_srv1'
+ - cloudflare_dns.result.record.type == 'SRV'
+ - cloudflare_dns.result.record.name == "_srv1._tcp.{{ cloudflare_dns_record }}.{{ cloudflare_zone }}"
+ - cloudflare_dns.result.record.zone_name == "{{ cloudflare_zone }}"
+
+- name: "Test: SRV record duplicate (old record present)"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: SRV
+ value: "srv1.{{ cloudflare_dns_record }}.{{ cloudflare_zone }}"
+ ttl: 300
+ priority: 10
+ service: srv1
+ proto: tcp
+ port: 3500
+ weight: 5
+ register: cloudflare_dns
+
+- name: "Validate: SRV record duplicate (old record present)"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - not cloudflare_dns|changed
+ - cloudflare_dns.result.record.content == '5\t3500\tsrv1.{{ cloudflare_dns_record }}.{{ cloudflare_zone }}'
+ - cloudflare_dns.result.record.ttl == 300
+ - cloudflare_dns.result.record.data.target == 'srv1.{{ cloudflare_dns_record }}.{{ cloudflare_zone }}'
+ - cloudflare_dns.result.record.data.port == 3500
+ - cloudflare_dns.result.record.data.weight == 5
+ - cloudflare_dns.result.record.data.priority == 10
+ - cloudflare_dns.result.record.data.name == "{{ cloudflare_dns_record }}"
+ - cloudflare_dns.result.record.data.proto == '_tcp'
+ - cloudflare_dns.result.record.data.service == '_srv1'
+ - cloudflare_dns.result.record.type == 'SRV'
+ - cloudflare_dns.result.record.name == "_srv1._tcp.{{ cloudflare_dns_record }}.{{ cloudflare_zone }}"
+ - cloudflare_dns.result.record.zone_name == "{{ cloudflare_zone }}"
+
+- name: "Test: SRV record duplicate (make new record solo)"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: SRV
+ value: "srv2.{{ cloudflare_dns_record }}.{{ cloudflare_zone }}"
+ ttl: 150
+ priority: 30
+ service: srv1
+ proto: tcp
+ port: 9999
+ weight: 19
+ solo: true
+ register: cloudflare_dns
+
+- name: "Validate: SRV record duplicate (make new record solo)"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - cloudflare_dns|changed
+ - cloudflare_dns.result.record.content == '19\t9999\tsrv2.{{ cloudflare_dns_record }}.{{ cloudflare_zone }}'
+ - cloudflare_dns.result.record.ttl == 150
+ - cloudflare_dns.result.record.data.target == 'srv2.{{ cloudflare_dns_record }}.{{ cloudflare_zone }}'
+ - cloudflare_dns.result.record.data.port == 9999
+ - cloudflare_dns.result.record.data.weight == 19
+ - cloudflare_dns.result.record.data.priority == 30
+ - cloudflare_dns.result.record.data.name == "{{ cloudflare_dns_record }}"
+ - cloudflare_dns.result.record.data.proto == '_tcp'
+ - cloudflare_dns.result.record.data.service == '_srv1'
+ - cloudflare_dns.result.record.type == 'SRV'
+ - cloudflare_dns.result.record.name == "_srv1._tcp.{{ cloudflare_dns_record }}.{{ cloudflare_zone }}"
+ - cloudflare_dns.result.record.zone_name == "{{ cloudflare_zone }}"
+
+- name: "Test: SRV record duplicate (old record absent)"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: SRV
+ value: "srv1.{{ cloudflare_dns_record }}.{{ cloudflare_zone }}"
+ ttl: 300
+ priority: 10
+ service: srv1
+ proto: tcp
+ port: 3500
+ weight: 5
+ state: absent
+ register: cloudflare_dns
+
+- name: "Validate: SRV record duplicate (old record absent)"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - not cloudflare_dns|changed
+
+- name: "Test: SRV record deletion"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: SRV
+ value: "srv2.{{ cloudflare_dns_record }}.{{ cloudflare_zone }}"
+ ttl: 150
+ priority: 30
+ service: srv1
+ proto: tcp
+ port: 9999
+ weight: 19
+ state: absent
+ register: cloudflare_dns
+
+- name: "Validate: SRV record deletion"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - cloudflare_dns|changed
+
+- name: "Test: SRV record deletion succeeded"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: SRV
+ value: "srv2.{{ cloudflare_dns_record }}.{{ cloudflare_zone }}"
+ ttl: 150
+ priority: 30
+ service: srv1
+ proto: tcp
+ port: 9999
+ weight: 19
+ state: absent
+ register: cloudflare_dns
+
+- name: "Validate: SRV record deletion succeeded"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - not cloudflare_dns|changed
diff --git a/test/integration/roles/test_cloudflare_dns/tasks/txt_record.yml b/test/integration/roles/test_cloudflare_dns/tasks/txt_record.yml
new file mode 100644
index 0000000000..6cd925e52e
--- /dev/null
+++ b/test/integration/roles/test_cloudflare_dns/tasks/txt_record.yml
@@ -0,0 +1,184 @@
+---
+######## TXT record tests #################
+
+- set_fact:
+ # values breaking the api: ,<>:
+ txt_teststring: 'abc123 !@#$%^&*()_+=-;./{}?\|'
+
+- name: "Test: TXT record creation"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: TXT
+ value: "{{ txt_teststring }}"
+ ttl: 150
+ register: cloudflare_dns
+
+- name: "Validate: TXT record creation"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - cloudflare_dns|changed
+ - cloudflare_dns.result.record.content == "{{ txt_teststring }}"
+ - cloudflare_dns.result.record.ttl == 150
+ - cloudflare_dns.result.record.type == 'TXT'
+ - cloudflare_dns.result.record.name == "{{ cloudflare_dns_record }}.{{ cloudflare_zone }}"
+ - cloudflare_dns.result.record.zone_name == "{{ cloudflare_zone }}"
+
+- name: "Test: TXT record idempotency"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: TXT
+ value: "{{ txt_teststring }}"
+ ttl: 150
+ register: cloudflare_dns
+
+- name: "Validate: TXT record idempotency"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - not cloudflare_dns|changed
+
+- name: "Test: TXT record update"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: TXT
+ value: "{{ txt_teststring }}"
+ ttl: 300
+ register: cloudflare_dns
+
+- name: "Validate: TXT record update"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - cloudflare_dns|changed
+ - cloudflare_dns.result.record.ttl == 300
+
+- name: "Test: TXT record duplicate (create new record)"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: TXT
+ value: 'teststring'
+ ttl: 150
+ register: cloudflare_dns
+
+- name: "Validate: TXT record duplicate (create new record)"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - cloudflare_dns|changed
+ - cloudflare_dns.result.record.content == 'teststring'
+ - cloudflare_dns.result.record.ttl == 150
+ - cloudflare_dns.result.record.type == 'TXT'
+ - cloudflare_dns.result.record.name == "{{ cloudflare_dns_record }}.{{ cloudflare_zone }}"
+ - cloudflare_dns.result.record.zone_name == "{{ cloudflare_zone }}"
+
+- name: "Test: TXT record duplicate (old record present)"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: TXT
+ value: "{{ txt_teststring }}"
+ ttl: 300
+ register: cloudflare_dns
+
+- name: "Validate: TXT record duplicate (old record present)"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - not cloudflare_dns|changed
+ - cloudflare_dns.result.record.content == "{{ txt_teststring }}"
+ - cloudflare_dns.result.record.ttl == 300
+ - cloudflare_dns.result.record.type == 'TXT'
+ - cloudflare_dns.result.record.name == "{{ cloudflare_dns_record }}.{{ cloudflare_zone }}"
+ - cloudflare_dns.result.record.zone_name == "{{ cloudflare_zone }}"
+
+- name: "Test: TXT record duplicate (make new record solo)"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: TXT
+ value: 'teststring'
+ ttl: 150
+ solo: true
+ register: cloudflare_dns
+
+- name: "Validate: TXT record duplicate (make new record solo)"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - cloudflare_dns|changed
+ - cloudflare_dns.result.record.content == 'teststring'
+ - cloudflare_dns.result.record.ttl == 150
+ - cloudflare_dns.result.record.type == 'TXT'
+ - cloudflare_dns.result.record.name == "{{ cloudflare_dns_record }}.{{ cloudflare_zone }}"
+ - cloudflare_dns.result.record.zone_name == "{{ cloudflare_zone }}"
+
+- name: "Test: TXT record duplicate (old record absent)"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: TXT
+ value: "{{ txt_teststring }}"
+ ttl: 300
+ state: absent
+ register: cloudflare_dns
+
+- name: "Validate: TXT record duplicate (old record absent)"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - not cloudflare_dns|changed
+
+- name: "Test: TXT record deletion"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: TXT
+ value: 'teststring'
+ ttl: 150
+ state: absent
+ register: cloudflare_dns
+
+- name: "Validate: TXT record deletion"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - cloudflare_dns|changed
+
+- name: "Test: TXT record deletion succeeded"
+ cloudflare_dns:
+ account_email: "{{ cloudflare_email }}"
+ account_api_token: "{{ cloudflare_api_token }}"
+ zone: "{{ cloudflare_zone }}"
+ record: "{{ cloudflare_dns_record }}"
+ type: TXT
+ value: 'teststring'
+ ttl: 150
+ state: absent
+ register: cloudflare_dns
+
+- name: "Validate: TXT record deletion succeeded"
+ assert:
+ that:
+ - cloudflare_dns|success
+ - not cloudflare_dns|changed
diff --git a/test/utils/tox/requirements.txt b/test/utils/tox/requirements.txt
index 34de42bc14..e4f4f03e3c 100644
--- a/test/utils/tox/requirements.txt
+++ b/test/utils/tox/requirements.txt
@@ -11,3 +11,4 @@ unittest2
redis
python-memcached
python-systemd
+pycrypto