diff options
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 |