summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2016-12-07 06:35:28 +0000
committerGerrit Code Review <review@openstack.org>2016-12-07 06:35:28 +0000
commitbb8846a3ab0dc92f31a343b7493ada533624a490 (patch)
treeb0b163c33ed1ec1ee60aa31ab434b5c33e0763b9
parentaed4dcd893e9c3860074655b1fc3c39bff7c7c4e (diff)
parentbabff882c0794fab81665744bcc1aa155b90eed2 (diff)
downloadoslo-config-bb8846a3ab0dc92f31a343b7493ada533624a490.tar.gz
Merge "Fixing HostName and adding support for HostAddress"
-rw-r--r--doc/source/opts.rst1
-rw-r--r--oslo_config/cfg.py15
-rw-r--r--oslo_config/generator.py3
-rw-r--r--oslo_config/sphinxext.py1
-rw-r--r--oslo_config/tests/test_types.py40
-rw-r--r--oslo_config/types.py61
-rw-r--r--releasenotes/notes/add-HostAddressOpt-6e7e2afe7c7863cb.yaml13
7 files changed, 129 insertions, 5 deletions
diff --git a/doc/source/opts.rst b/doc/source/opts.rst
index e82ecff..ccdf9a3 100644
--- a/doc/source/opts.rst
+++ b/doc/source/opts.rst
@@ -16,6 +16,7 @@ Option Definitions
.. autoclass:: IPOpt
.. autoclass:: PortOpt
.. autoclass:: HostnameOpt
+.. autoclass:: HostAddressOpt
.. autoclass:: URIOpt
.. autoclass:: DeprecatedOpt
.. autoclass:: SubCommandOpt
diff --git a/oslo_config/cfg.py b/oslo_config/cfg.py
index b7b10bf..33db907 100644
--- a/oslo_config/cfg.py
+++ b/oslo_config/cfg.py
@@ -59,6 +59,7 @@ Type Option
:class:`oslo_config.types.Dict` :class:`oslo_config.cfg.DictOpt`
:class:`oslo_config.types.IPAddress` :class:`oslo_config.cfg.IPOpt`
: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`
==================================== ======
@@ -1391,6 +1392,20 @@ class HostnameOpt(Opt):
**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):
"""Opt with URI type
diff --git a/oslo_config/generator.py b/oslo_config/generator.py
index 8643631..2760045 100644
--- a/oslo_config/generator.py
+++ b/oslo_config/generator.py
@@ -90,7 +90,8 @@ def _format_defaults(opt):
elif opt.default is None:
default_str = '<None>'
elif (isinstance(opt, (cfg.StrOpt, cfg.IPOpt,
- cfg.HostnameOpt, cfg.URIOpt))):
+ cfg.HostnameOpt, cfg.HostAddressOpt,
+ cfg.URIOpt))):
default_str = opt.default
elif isinstance(opt, cfg.BoolOpt):
default_str = str(opt.default).lower()
diff --git a/oslo_config/sphinxext.py b/oslo_config/sphinxext.py
index cd74187..8439c07 100644
--- a/oslo_config/sphinxext.py
+++ b/oslo_config/sphinxext.py
@@ -77,6 +77,7 @@ _TYPE_DESCRIPTIONS = {
cfg.PortOpt: 'port number',
cfg.HostnameOpt: 'hostname',
cfg.URIOpt: 'URI',
+ cfg.HostAddressOpt: 'host address',
cfg._ConfigFileOpt: 'list of filenames',
cfg._ConfigDirOpt: 'list of directory names',
}
diff --git a/oslo_config/tests/test_types.py b/oslo_config/tests/test_types.py
index 2389d9b..96f101b 100644
--- a/oslo_config/tests/test_types.py
+++ b/oslo_config/tests/test_types.py
@@ -707,6 +707,32 @@ class IPv6AddressTypeTests(IPAddressTypeTests):
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):
type = types.Hostname()
@@ -746,6 +772,12 @@ class HostnameTypeTests(TypeTestHelper, unittest.TestCase):
self.assertInvalid(".host.name.com")
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):
self.assertInvalid("-host.com")
self.assertInvalid("-hostname.com-")
@@ -759,9 +791,13 @@ class HostnameTypeTests(TypeTestHelper, unittest.TestCase):
self.assertConvertedEqual('cell1.nova.site1')
self.assertConvertedEqual('site01001')
self.assertConvertedEqual('home-site-here.org.com')
- self.assertConvertedEqual('192.168.0.1')
- self.assertConvertedEqual('1.1.1')
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):
self.assertConvertedEqual('host.%s.com' % ('x' * 63))
diff --git a/oslo_config/types.py b/oslo_config/types.py
index 4096f2e..6cb84e2 100644
--- a/oslo_config/types.py
+++ b/oslo_config/types.py
@@ -704,7 +704,7 @@ class IPAddress(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
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
- Consists only of allowed characters: letters (A-Z and a-z),
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
- 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:
@@ -738,6 +742,10 @@ class Hostname(ConfigType):
if value.endswith("."):
value = value[:-1]
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(".")):
raise ValueError("%s is an invalid hostname" % value)
return value
@@ -752,6 +760,55 @@ class Hostname(ConfigType):
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):
"""URI type
diff --git a/releasenotes/notes/add-HostAddressOpt-6e7e2afe7c7863cb.yaml b/releasenotes/notes/add-HostAddressOpt-6e7e2afe7c7863cb.yaml
new file mode 100644
index 0000000..576f464
--- /dev/null
+++ b/releasenotes/notes/add-HostAddressOpt-6e7e2afe7c7863cb.yaml
@@ -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.