diff --git a/doc/source/opts.rst b/doc/source/opts.rst index e82ecff0..ccdf9a3c 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 b7b10bf8..33db907b 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 8643631b..27600455 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 = '' 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 cd741871..8439c07a 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 2389d9b2..96f101bd 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 4096f2eb..6cb84e29 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}(? + 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.