diff --git a/lib/ansible/plugins/action/add_host.py b/lib/ansible/plugins/action/add_host.py index 12c9febe95d..cf2dab17373 100644 --- a/lib/ansible/plugins/action/add_host.py +++ b/lib/ansible/plugins/action/add_host.py @@ -20,6 +20,8 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type +import re + from ansible.plugins.action import ActionBase class ActionModule(ActionBase): @@ -38,8 +40,8 @@ class ActionModule(ActionBase): new_name = self._task.args.get('name', self._task.args.get('hostname', None)) #vv("creating host via 'add_host': hostname=%s" % new_name) - if ":" in new_name: - new_name, new_port = new_name.split(":") + new_name, new_port = _parse_ip_host_and_port(new_name) + if new_port: self._task.args['ansible_ssh_port'] = new_port groups = self._task.args.get('groupname', self._task.args.get('groups', self._task.args.get('group', ''))) @@ -58,4 +60,26 @@ class ActionModule(ActionBase): return dict(changed=True, add_host=dict(host_name=new_name, groups=new_groups, host_vars=host_vars)) +def _parse_ip_host_and_port(hostname): + """ + Attempt to parse the hostname and port from a hostname, e.g., + some-host-name + some-host-name:80 + 8.8.8.8 + 8.8.8.8:80 + 2001:db8:0:1 + [2001:db8:0:1]:80 + """ + if hostname.count(':') > 1: + match = re.match( + '\[(?P[^\]]+)\](:(?P[0-9]+))?', + hostname + ) + if match: + return match.group('ip'), match.group('port') + else: + return hostname, None + elif ':' in hostname: + return hostname.rsplit(':', 1) + return hostname, None diff --git a/test/units/plugins/action/test_add_host.py b/test/units/plugins/action/test_add_host.py new file mode 100644 index 00000000000..c694d387a3a --- /dev/null +++ b/test/units/plugins/action/test_add_host.py @@ -0,0 +1,47 @@ +import unittest + +from ansible.plugins.action import add_host + + +class TestAddHost(unittest.TestCase): + + def test_hostname(self): + host, port = add_host._parse_ip_host_and_port('some-remote-host') + assert host == 'some-remote-host' + assert port is None + + def test_hostname_with_port(self): + host, port = add_host._parse_ip_host_and_port('some-remote-host:80') + assert host == 'some-remote-host' + assert port == '80' + + def test_parse_ip_host_and_port_v4(self): + host, port = add_host._parse_ip_host_and_port('8.8.8.8') + assert host == '8.8.8.8' + assert port is None + + def test_parse_ip_host_and_port_v4_and_port(self): + host, port = add_host._parse_ip_host_and_port('8.8.8.8:80') + assert host == '8.8.8.8' + assert port == '80' + + def test_parse_ip_host_and_port_v6(self): + host, port = add_host._parse_ip_host_and_port( + 'dead:beef:dead:beef:dead:beef:dead:beef' + ) + assert host == 'dead:beef:dead:beef:dead:beef:dead:beef' + assert port is None + + def test_parse_ip_host_and_port_v6_with_brackets(self): + host, port = add_host._parse_ip_host_and_port( + '[dead:beef:dead:beef:dead:beef:dead:beef]' + ) + assert host == 'dead:beef:dead:beef:dead:beef:dead:beef' + assert port is None + + def test_parse_ip_host_and_port_v6_with_brackets_and_port(self): + host, port = add_host._parse_ip_host_and_port( + '[dead:beef:dead:beef:dead:beef:dead:beef]:80' + ) + assert host == 'dead:beef:dead:beef:dead:beef:dead:beef' + assert port == '80'