mirror of
https://github.com/ansible-collections/community.general.git
synced 2026-05-07 05:42:50 +00:00
Be systematic about parsing and validating hostnames and addresses
This adds a parse_address(pattern) utility function that returns (host,port), and uses it wherever where we accept IPv4 and IPv6 addresses and hostnames (or host patterns): the inventory parser the the add_host action plugin. It also introduces a more extensive set of unit tests that supersedes the old add_host unit tests (which didn't actually test add_host, but only the parsing function).
This commit is contained in:
@@ -33,6 +33,7 @@ from ansible.inventory.group import Group
|
||||
from ansible.inventory.host import Host
|
||||
from ansible.plugins import vars_loader
|
||||
from ansible.utils.vars import combine_vars
|
||||
from ansible.parsing.utils.addresses import parse_address
|
||||
|
||||
try:
|
||||
from __main__ import display
|
||||
@@ -83,27 +84,16 @@ class Inventory(object):
|
||||
host_list = host_list.split(",")
|
||||
host_list = [ h for h in host_list if h and h.strip() ]
|
||||
|
||||
self.parser = None
|
||||
|
||||
if host_list is None:
|
||||
self.parser = None
|
||||
pass
|
||||
elif isinstance(host_list, list):
|
||||
self.parser = None
|
||||
all = Group('all')
|
||||
self.groups = [ all ]
|
||||
ipv6_re = re.compile('\[([a-f:A-F0-9]*[%[0-z]+]?)\](?::(\d+))?')
|
||||
for x in host_list:
|
||||
m = ipv6_re.match(x)
|
||||
if m:
|
||||
all.add_host(Host(m.groups()[0], m.groups()[1]))
|
||||
else:
|
||||
if ":" in x:
|
||||
tokens = x.rsplit(":", 1)
|
||||
# if there is ':' in the address, then this is an ipv6
|
||||
if ':' in tokens[0]:
|
||||
all.add_host(Host(x))
|
||||
else:
|
||||
all.add_host(Host(tokens[0], tokens[1]))
|
||||
else:
|
||||
all.add_host(Host(x))
|
||||
for h in host_list:
|
||||
(host, port) = parse_address(h, allow_ranges=False)
|
||||
all.add_host(Host(host, port))
|
||||
elif self._loader.path_exists(host_list):
|
||||
#TODO: switch this to a plugin loader and a 'condition' per plugin on which it should be tried, restoring 'inventory pllugins'
|
||||
if self._loader.is_directory(host_list):
|
||||
|
||||
@@ -44,7 +44,7 @@ def detect_range(line = None):
|
||||
|
||||
Returnes True if the given line contains a pattern, else False.
|
||||
'''
|
||||
if 0 <= line.find("[") < line.find(":") < line.find("]"):
|
||||
if '[' in line:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@@ -29,6 +29,7 @@ from ansible.inventory.host import Host
|
||||
from ansible.inventory.group import Group
|
||||
from ansible.inventory.expand_hosts import detect_range
|
||||
from ansible.inventory.expand_hosts import expand_hostname_range
|
||||
from ansible.parsing.utils.addresses import parse_address
|
||||
from ansible.utils.unicode import to_unicode, to_bytes
|
||||
|
||||
class InventoryParser(object):
|
||||
@@ -265,30 +266,20 @@ class InventoryParser(object):
|
||||
optional port number that applies to all of them.
|
||||
'''
|
||||
|
||||
# Is a port number specified?
|
||||
#
|
||||
# This may be a mandatory :NN suffix on any square-bracketed expression
|
||||
# (IPv6 address, IPv4 address, host name, host pattern), or an optional
|
||||
# :NN suffix on an IPv4 address, host name, or pattern. IPv6 addresses
|
||||
# must be in square brackets if a port is specified.
|
||||
# Can the given hostpattern be parsed as a host with an optional port
|
||||
# specification?
|
||||
|
||||
port = None
|
||||
(pattern, port) = parse_address(hostpattern, allow_ranges=True)
|
||||
if not pattern:
|
||||
self._raise_error("Can't parse '%s' as host[:port]" % hostpattern)
|
||||
|
||||
for type in ['bracketed_hostport', 'hostport']:
|
||||
m = self.patterns[type].match(hostpattern)
|
||||
if m:
|
||||
(hostpattern, port) = m.groups()
|
||||
continue
|
||||
# Once we have separated the pattern, we expand it into list of one or
|
||||
# more hostnames, depending on whether it contains any [x:y] ranges.
|
||||
|
||||
# Now we're left with just the pattern, which results in a list of one
|
||||
# or more hostnames, depending on whether it contains any [x:y] ranges.
|
||||
#
|
||||
# FIXME: We could be more strict here about validation.
|
||||
|
||||
if detect_range(hostpattern):
|
||||
hostnames = expand_hostname_range(hostpattern)
|
||||
if detect_range(pattern):
|
||||
hostnames = expand_hostname_range(pattern)
|
||||
else:
|
||||
hostnames = [hostpattern]
|
||||
hostnames = [pattern]
|
||||
|
||||
return (hostnames, port)
|
||||
|
||||
@@ -374,29 +365,3 @@ class InventoryParser(object):
|
||||
$ # end of the line
|
||||
''', re.X
|
||||
)
|
||||
|
||||
# The following patterns match the various ways in which a port number
|
||||
# may be specified on an IPv6 address, IPv4 address, hostname, or host
|
||||
# pattern. All of the above may be enclosed in square brackets with a
|
||||
# mandatory :NN suffix; or all but the first may be given without any
|
||||
# brackets but with an :NN suffix.
|
||||
|
||||
self.patterns['bracketed_hostport'] = re.compile(
|
||||
r'''^
|
||||
\[(.+)\] # [host identifier]
|
||||
:([0-9]+) # :port number
|
||||
$
|
||||
''', re.X
|
||||
)
|
||||
|
||||
self.patterns['hostport'] = re.compile(
|
||||
r'''^
|
||||
((?: # We want to match:
|
||||
[^:\[\]] # (a non-range character
|
||||
| # ...or...
|
||||
\[[^\]]*\] # a complete bracketed expression)
|
||||
)*) # repeated as many times as possible
|
||||
:([0-9]+) # followed by a port number
|
||||
$
|
||||
''', re.X
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user