Merge "Fixing HostName and adding support for HostAddress"
This commit is contained in:
commit
bb8846a3ab
@ -16,6 +16,7 @@ Option Definitions
|
|||||||
.. autoclass:: IPOpt
|
.. autoclass:: IPOpt
|
||||||
.. autoclass:: PortOpt
|
.. autoclass:: PortOpt
|
||||||
.. autoclass:: HostnameOpt
|
.. autoclass:: HostnameOpt
|
||||||
|
.. autoclass:: HostAddressOpt
|
||||||
.. autoclass:: URIOpt
|
.. autoclass:: URIOpt
|
||||||
.. autoclass:: DeprecatedOpt
|
.. autoclass:: DeprecatedOpt
|
||||||
.. autoclass:: SubCommandOpt
|
.. autoclass:: SubCommandOpt
|
||||||
|
@ -59,6 +59,7 @@ Type Option
|
|||||||
:class:`oslo_config.types.Dict` :class:`oslo_config.cfg.DictOpt`
|
:class:`oslo_config.types.Dict` :class:`oslo_config.cfg.DictOpt`
|
||||||
:class:`oslo_config.types.IPAddress` :class:`oslo_config.cfg.IPOpt`
|
:class:`oslo_config.types.IPAddress` :class:`oslo_config.cfg.IPOpt`
|
||||||
:class:`oslo_config.types.Hostname` :class:`oslo_config.cfg.HostnameOpt`
|
:class:`oslo_config.types.Hostname` :class:`oslo_config.cfg.HostnameOpt`
|
||||||
|
:class:`oslo_config.types.HostAddress`:class:`oslo_config.cfg.HostAddressOpt`
|
||||||
:class:`oslo_config.types.URI` :class:`oslo_config.cfg.URIOpt`
|
:class:`oslo_config.types.URI` :class:`oslo_config.cfg.URIOpt`
|
||||||
==================================== ======
|
==================================== ======
|
||||||
|
|
||||||
@ -1391,6 +1392,20 @@ class HostnameOpt(Opt):
|
|||||||
**kwargs)
|
**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class HostAddressOpt(Opt):
|
||||||
|
"""Option for either an IP or a hostname.
|
||||||
|
|
||||||
|
Accepts valid hostnames and valid IP addresses.
|
||||||
|
|
||||||
|
.. versionadded:: 3.22
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, name, version=None, **kwargs):
|
||||||
|
super(HostAddressOpt, self).__init__(name,
|
||||||
|
type=types.HostAddress(version),
|
||||||
|
**kwargs)
|
||||||
|
|
||||||
|
|
||||||
class URIOpt(Opt):
|
class URIOpt(Opt):
|
||||||
|
|
||||||
"""Opt with URI type
|
"""Opt with URI type
|
||||||
|
@ -90,7 +90,8 @@ def _format_defaults(opt):
|
|||||||
elif opt.default is None:
|
elif opt.default is None:
|
||||||
default_str = '<None>'
|
default_str = '<None>'
|
||||||
elif (isinstance(opt, (cfg.StrOpt, cfg.IPOpt,
|
elif (isinstance(opt, (cfg.StrOpt, cfg.IPOpt,
|
||||||
cfg.HostnameOpt, cfg.URIOpt))):
|
cfg.HostnameOpt, cfg.HostAddressOpt,
|
||||||
|
cfg.URIOpt))):
|
||||||
default_str = opt.default
|
default_str = opt.default
|
||||||
elif isinstance(opt, cfg.BoolOpt):
|
elif isinstance(opt, cfg.BoolOpt):
|
||||||
default_str = str(opt.default).lower()
|
default_str = str(opt.default).lower()
|
||||||
|
@ -77,6 +77,7 @@ _TYPE_DESCRIPTIONS = {
|
|||||||
cfg.PortOpt: 'port number',
|
cfg.PortOpt: 'port number',
|
||||||
cfg.HostnameOpt: 'hostname',
|
cfg.HostnameOpt: 'hostname',
|
||||||
cfg.URIOpt: 'URI',
|
cfg.URIOpt: 'URI',
|
||||||
|
cfg.HostAddressOpt: 'host address',
|
||||||
cfg._ConfigFileOpt: 'list of filenames',
|
cfg._ConfigFileOpt: 'list of filenames',
|
||||||
cfg._ConfigDirOpt: 'list of directory names',
|
cfg._ConfigDirOpt: 'list of directory names',
|
||||||
}
|
}
|
||||||
|
@ -707,6 +707,32 @@ class IPv6AddressTypeTests(IPAddressTypeTests):
|
|||||||
self.assertInvalid('192.168.0.1')
|
self.assertInvalid('192.168.0.1')
|
||||||
|
|
||||||
|
|
||||||
|
class HostAddressTypeTests(TypeTestHelper, unittest.TestCase):
|
||||||
|
type = types.HostAddress()
|
||||||
|
|
||||||
|
def test_invalid_host_addresses(self):
|
||||||
|
self.assertInvalid('-1')
|
||||||
|
self.assertInvalid('_foo')
|
||||||
|
self.assertInvalid('3.14')
|
||||||
|
self.assertInvalid('10.0')
|
||||||
|
self.assertInvalid('host..name')
|
||||||
|
self.assertInvalid('org.10')
|
||||||
|
self.assertInvalid('0.0.00')
|
||||||
|
|
||||||
|
def test_valid_host_addresses(self):
|
||||||
|
self.assertConvertedValue('foo.bar', 'foo.bar')
|
||||||
|
self.assertConvertedValue('192.168.0.1', '192.168.0.1')
|
||||||
|
self.assertConvertedValue('abcd:ef::1', 'abcd:ef::1')
|
||||||
|
self.assertConvertedValue('home-site-here.org.com',
|
||||||
|
'home-site-here.org.com')
|
||||||
|
self.assertConvertedValue('3com.com', '3com.com')
|
||||||
|
self.assertConvertedValue('10.org', '10.org')
|
||||||
|
self.assertConvertedValue('cell1.nova.site1', 'cell1.nova.site1')
|
||||||
|
self.assertConvertedValue('ab-c.com', 'ab-c.com')
|
||||||
|
self.assertConvertedValue('abc.com-org', 'abc.com-org')
|
||||||
|
self.assertConvertedValue('abc.0-0', 'abc.0-0')
|
||||||
|
|
||||||
|
|
||||||
class HostnameTypeTests(TypeTestHelper, unittest.TestCase):
|
class HostnameTypeTests(TypeTestHelper, unittest.TestCase):
|
||||||
type = types.Hostname()
|
type = types.Hostname()
|
||||||
|
|
||||||
@ -746,6 +772,12 @@ class HostnameTypeTests(TypeTestHelper, unittest.TestCase):
|
|||||||
self.assertInvalid(".host.name.com")
|
self.assertInvalid(".host.name.com")
|
||||||
self.assertInvalid("no spaces")
|
self.assertInvalid("no spaces")
|
||||||
|
|
||||||
|
def test_invalid_hostnames_with_numeric_characters(self):
|
||||||
|
self.assertInvalid("10.0.0.0")
|
||||||
|
self.assertInvalid("3.14")
|
||||||
|
self.assertInvalid("org.10")
|
||||||
|
self.assertInvalid('0.0.00')
|
||||||
|
|
||||||
def test_no_start_end_hyphens(self):
|
def test_no_start_end_hyphens(self):
|
||||||
self.assertInvalid("-host.com")
|
self.assertInvalid("-host.com")
|
||||||
self.assertInvalid("-hostname.com-")
|
self.assertInvalid("-hostname.com-")
|
||||||
@ -759,9 +791,13 @@ class HostnameTypeTests(TypeTestHelper, unittest.TestCase):
|
|||||||
self.assertConvertedEqual('cell1.nova.site1')
|
self.assertConvertedEqual('cell1.nova.site1')
|
||||||
self.assertConvertedEqual('site01001')
|
self.assertConvertedEqual('site01001')
|
||||||
self.assertConvertedEqual('home-site-here.org.com')
|
self.assertConvertedEqual('home-site-here.org.com')
|
||||||
self.assertConvertedEqual('192.168.0.1')
|
|
||||||
self.assertConvertedEqual('1.1.1')
|
|
||||||
self.assertConvertedEqual('localhost')
|
self.assertConvertedEqual('localhost')
|
||||||
|
self.assertConvertedEqual('3com.com')
|
||||||
|
self.assertConvertedEqual('10.org')
|
||||||
|
self.assertConvertedEqual('10ab.10ab')
|
||||||
|
self.assertConvertedEqual('ab-c.com')
|
||||||
|
self.assertConvertedEqual('abc.com-org')
|
||||||
|
self.assertConvertedEqual('abc.0-0')
|
||||||
|
|
||||||
def test_max_segment_size(self):
|
def test_max_segment_size(self):
|
||||||
self.assertConvertedEqual('host.%s.com' % ('x' * 63))
|
self.assertConvertedEqual('host.%s.com' % ('x' * 63))
|
||||||
|
@ -704,7 +704,7 @@ class IPAddress(ConfigType):
|
|||||||
|
|
||||||
|
|
||||||
class Hostname(ConfigType):
|
class Hostname(ConfigType):
|
||||||
"""Hostname type.
|
"""Host domain name type.
|
||||||
|
|
||||||
A hostname refers to a valid DNS or hostname. It must not be longer than
|
A hostname refers to a valid DNS or hostname. It must not be longer than
|
||||||
253 characters, have a segment greater than 63 characters, nor start or
|
253 characters, have a segment greater than 63 characters, nor start or
|
||||||
@ -724,10 +724,14 @@ class Hostname(ConfigType):
|
|||||||
- Contains at least one character and a maximum of 63 characters
|
- Contains at least one character and a maximum of 63 characters
|
||||||
- Consists only of allowed characters: letters (A-Z and a-z),
|
- Consists only of allowed characters: letters (A-Z and a-z),
|
||||||
digits (0-9), and hyphen (-)
|
digits (0-9), and hyphen (-)
|
||||||
|
- Ensures that the final segment (representing the top level domain
|
||||||
|
name) contains at least one non-numeric character
|
||||||
- Does not begin or end with a hyphen
|
- Does not begin or end with a hyphen
|
||||||
- maximum total length of 253 characters
|
- maximum total length of 253 characters
|
||||||
|
|
||||||
For more details , please see: http://tools.ietf.org/html/rfc1035
|
For more details , please see: http://tools.ietf.org/html/rfc1035,
|
||||||
|
https://www.ietf.org/rfc/rfc1912, and
|
||||||
|
https://tools.ietf.org/html/rfc1123
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if len(value) == 0:
|
if len(value) == 0:
|
||||||
@ -738,6 +742,10 @@ class Hostname(ConfigType):
|
|||||||
if value.endswith("."):
|
if value.endswith("."):
|
||||||
value = value[:-1]
|
value = value[:-1]
|
||||||
allowed = re.compile("(?!-)[A-Z0-9-]{1,63}(?<!-)$", re.IGNORECASE)
|
allowed = re.compile("(?!-)[A-Z0-9-]{1,63}(?<!-)$", re.IGNORECASE)
|
||||||
|
if not re.search('[a-zA-Z-]', value.split(".")[-1]):
|
||||||
|
raise ValueError('%s contains no non-numeric characters in the '
|
||||||
|
'top-level domain part of the host name and is '
|
||||||
|
'invalid' % value)
|
||||||
if any((not allowed.match(x)) for x in value.split(".")):
|
if any((not allowed.match(x)) for x in value.split(".")):
|
||||||
raise ValueError("%s is an invalid hostname" % value)
|
raise ValueError("%s is an invalid hostname" % value)
|
||||||
return value
|
return value
|
||||||
@ -752,6 +760,55 @@ class Hostname(ConfigType):
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
class HostAddress(object):
|
||||||
|
"""Host Address type.
|
||||||
|
|
||||||
|
Represents both valid IP addresses and valid host domain names
|
||||||
|
including fully qualified domain names.
|
||||||
|
Performs strict checks for both IP addresses and valid hostnames,
|
||||||
|
matching the opt values to the respective types as per RFC1912.
|
||||||
|
|
||||||
|
:param version: defines which version should be explicitly
|
||||||
|
checked (4 or 6) in case of an IP address
|
||||||
|
:param type_name: Type name to be used in the sample config file.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, version=None, type_name='host address value'):
|
||||||
|
"""Check for valid version in case an IP address is provided
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.ip_address = IPAddress(version, type_name)
|
||||||
|
self.hostname = Hostname('localhost')
|
||||||
|
|
||||||
|
def __call__(self, value):
|
||||||
|
"""Checks if is a valid IP/hostname.
|
||||||
|
|
||||||
|
If not a valid IP, makes sure it is not a mistyped IP before
|
||||||
|
performing checks for it as a hostname.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
value = self.ip_address(value)
|
||||||
|
except ValueError:
|
||||||
|
try:
|
||||||
|
value = self.hostname(value)
|
||||||
|
except ValueError:
|
||||||
|
raise ValueError("%s is not a valid host address", value)
|
||||||
|
return value
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return 'HostAddress'
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self.__class__ == other.__class__
|
||||||
|
|
||||||
|
def _formatter(self, value):
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
class URI(ConfigType):
|
class URI(ConfigType):
|
||||||
|
|
||||||
"""URI type
|
"""URI type
|
||||||
|
13
releasenotes/notes/add-HostAddressOpt-6e7e2afe7c7863cb.yaml
Normal file
13
releasenotes/notes/add-HostAddressOpt-6e7e2afe7c7863cb.yaml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
prelude: >
|
||||||
|
Configuration option type of ``HostAddressOpt`` added to accept and
|
||||||
|
validate both IP addresses and hostnames. Please refer to the
|
||||||
|
``features`` section for more information.
|
||||||
|
|
||||||
|
features:
|
||||||
|
- Configuration option type of ``HostAddressOpt`` added to accept both
|
||||||
|
valid IP address (IPv4 and IPv6) values as well as hostnames.
|
||||||
|
The ``HostAddressOpt`` will accept both IPv4 and IPv6 addresses
|
||||||
|
and ensure that strict checks are performed on the IP versions.
|
||||||
|
This option type will also accept and accurately validate hostnames
|
||||||
|
ensuring that no invalid IP passes as a valid hostname.
|
Loading…
x
Reference in New Issue
Block a user