diff options
author | Tom Cammann <tom.cammann@hp.com> | 2015-10-22 13:40:16 +0100 |
---|---|---|
committer | Alexis Lee <lxsli@hpe.com> | 2016-02-18 09:58:40 +0000 |
commit | f6c668bfb32cbde63866541e8c2100eda5fa5c66 (patch) | |
tree | 5ffc4d011459de557218f4f76da42aa6318fcf6e | |
parent | 3dfd4a4e657426534b132cbe0c2dbed554d060f2 (diff) | |
download | oslo-config-f6c668bfb32cbde63866541e8c2100eda5fa5c66.tar.gz |
Add hostname config type
Hostnames are often used in config files and they have specific
validation requirements. This change adds a config to validate a
correct hostname are specified in a config file.
A hostname refers to a valid DNS or hostname. It must not be longer than
253 characters, have a segment greater than 63 characters and start or
end with a hyphen.
Closes-Bug: #1508943
Co-Authored-By: ChangBo Guo(gcb) <eric.guo@easystack.cn>
Change-Id: Ic1028a437aee6076c4b7f437f60bbd209f38a20e
-rw-r--r-- | doc/source/opts.rst | 1 | ||||
-rw-r--r-- | oslo_config/cfg.py | 15 | ||||
-rw-r--r-- | oslo_config/generator.py | 3 | ||||
-rw-r--r-- | oslo_config/tests/test_generator.py | 14 | ||||
-rw-r--r-- | oslo_config/tests/test_types.py | 68 | ||||
-rw-r--r-- | oslo_config/types.py | 49 |
6 files changed, 149 insertions, 1 deletions
diff --git a/doc/source/opts.rst b/doc/source/opts.rst index af83367..c3e166f 100644 --- a/doc/source/opts.rst +++ b/doc/source/opts.rst @@ -15,6 +15,7 @@ Option Definitions .. autoclass:: MultiStrOpt .. autoclass:: IPOpt .. autoclass:: PortOpt +.. autoclass:: HostnameOpt .. autoclass:: DeprecatedOpt .. autoclass:: SubCommandOpt .. autoclass:: OptGroup diff --git a/oslo_config/cfg.py b/oslo_config/cfg.py index 756a594..a77d079 100644 --- a/oslo_config/cfg.py +++ b/oslo_config/cfg.py @@ -58,6 +58,7 @@ Type Option :class:`oslo_config.types.List` :class:`oslo_config.cfg.ListOpt` :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` ==================================== ====== For :class:`oslo_config.cfg.MultiOpt` the `item_type` parameter defines @@ -1251,6 +1252,20 @@ class PortOpt(Opt): super(PortOpt, self).__init__(name, type=type, **kwargs) +class HostnameOpt(Opt): + + """Option for a hostname. Only accepts valid hostnames. + + Option with ``type`` :class:`oslo_config.types.Hostname` + + .. versionadded:: 3.8 + """ + + def __init__(self, name, **kwargs): + super(HostnameOpt, self).__init__(name, type=types.Hostname(), + **kwargs) + + class MultiOpt(Opt): """Multi-value option. diff --git a/oslo_config/generator.py b/oslo_config/generator.py index 4ade144..3ec8c29 100644 --- a/oslo_config/generator.py +++ b/oslo_config/generator.py @@ -77,7 +77,8 @@ def _format_defaults(opt): elif opt.default is None: default_str = '<None>' elif (isinstance(opt, cfg.StrOpt) or - isinstance(opt, cfg.IPOpt)): + isinstance(opt, cfg.IPOpt) or + isinstance(opt, cfg.HostnameOpt)): default_str = opt.default elif isinstance(opt, cfg.BoolOpt): default_str = str(opt.default).lower() diff --git a/oslo_config/tests/test_generator.py b/oslo_config/tests/test_generator.py index 3127a99..fda2790 100644 --- a/oslo_config/tests/test_generator.py +++ b/oslo_config/tests/test_generator.py @@ -142,6 +142,9 @@ class GeneratorTestCase(base.BaseTestCase): 'port_opt': cfg.PortOpt('port_opt', default=80, help='a port'), + 'hostname_opt': cfg.HostnameOpt('hostname_opt', + default='compute01.nova.site1', + help='a hostname'), 'multi_opt': cfg.MultiStrOpt('multi_opt', default=['1', '2', '3'], help='multiple strings'), @@ -605,6 +608,17 @@ class GeneratorTestCase(base.BaseTestCase): # Maximum value: 65535 #port_opt = 80 ''')), + ('hostname_opt', + dict(opts=[('test', [(None, [opts['hostname_opt']])])], + expected='''[DEFAULT] + +# +# From test +# + +# a hostname (hostname value) +#hostname_opt = compute01.nova.site1 +''')), ('multi_opt', dict(opts=[('test', [(None, [opts['multi_opt']])])], expected='''[DEFAULT] diff --git a/oslo_config/tests/test_types.py b/oslo_config/tests/test_types.py index 7909733..b8c5d77 100644 --- a/oslo_config/tests/test_types.py +++ b/oslo_config/tests/test_types.py @@ -551,3 +551,71 @@ class IPv6AddressTypeTests(IPAddressTypeTests): def test_ipv4_address(self): self.assertInvalid('192.168.0.1') + + +class HostnameTypeTests(TypeTestHelper, unittest.TestCase): + type = types.Hostname() + + def assertConvertedEqual(self, value): + self.assertConvertedValue(value, value) + + def test_empty_hostname_fails(self): + self.assertInvalid('') + + def test_should_return_same_hostname_if_valid(self): + self.assertConvertedEqual('foo.bar') + + def test_trailing_quote_is_invalid(self): + self.assertInvalid('foo.bar"') + + def test_repr(self): + self.assertEqual('Hostname', repr(types.Hostname())) + + def test_equal(self): + self.assertEqual(types.Hostname(), types.Hostname()) + self.assertEqual(types.Hostname(), types.Hostname()) + + def test_not_equal_to_other_class(self): + self.assertNotEqual(types.Hostname(), types.Integer()) + self.assertNotEqual(types.Hostname(), types.String()) + + def test_invalid_characters(self): + self.assertInvalid('"host"') + self.assertInvalid("h'ost'") + self.assertInvalid("h'ost") + self.assertInvalid("h$ost") + self.assertInvalid("h%ost") + self.assertInvalid("host_01.co.uk") + self.assertInvalid("host;name=99") + self.assertInvalid('___site0.1001') + self.assertInvalid('_site01001') + self.assertInvalid("host..name") + self.assertInvalid(".host.name.com") + self.assertInvalid("no spaces") + + def test_no_start_end_hyphens(self): + self.assertInvalid("-host.com") + self.assertInvalid("-hostname.com-") + self.assertInvalid("hostname.co.uk-") + + def test_strip_trailing_dot(self): + self.assertConvertedValue('cell1.nova.site1.', 'cell1.nova.site1') + self.assertConvertedValue('cell1.', 'cell1') + + def test_valid_hostname(self): + 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') + + def test_max_segment_size(self): + self.assertConvertedEqual('host.%s.com' % ('x' * 63)) + self.assertInvalid('host.%s.com' % ('x' * 64)) + + def test_max_hostname_size(self): + test_str = '.'.join('x'*31 for x in range(8)) + self.assertEqual(255, len(test_str)) + self.assertInvalid(test_str) + self.assertConvertedEqual(test_str[:-2]) diff --git a/oslo_config/types.py b/oslo_config/types.py index bc4f777..2464c5c 100644 --- a/oslo_config/types.py +++ b/oslo_config/types.py @@ -603,3 +603,52 @@ class IPAddress(ConfigType): def _formatter(self, value): return value + + +class Hostname(ConfigType): + """Hostname 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 + end with a hyphen. + + :param type_name: Type name to be used in the sample config file. + + """ + + def __init__(self, type_name='hostname value'): + super(Hostname, self).__init__(type_name=type_name) + + def __call__(self, value): + """Check hostname is valid. + + Ensures that each segment + - 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 (-) + - 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 + """ + + if len(value) == 0: + raise ValueError("Cannot have an empty hostname") + if len(value) > 253: + raise ValueError("hostname is greater than 253 characters: %s" + % value) + if value.endswith("."): + value = value[:-1] + allowed = re.compile("(?!-)[A-Z0-9-]{1,63}(?<!-)$", re.IGNORECASE) + if any((not allowed.match(x)) for x in value.split(".")): + raise ValueError("%s is an invalid hostname" % value) + return value + + def __repr__(self): + return 'Hostname' + + def __eq__(self, other): + return self.__class__ == other.__class__ + + def _formatter(self, value): + return value |