summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Cammann <tom.cammann@hp.com>2015-10-22 13:40:16 +0100
committerAlexis Lee <lxsli@hpe.com>2016-02-18 09:58:40 +0000
commitf6c668bfb32cbde63866541e8c2100eda5fa5c66 (patch)
tree5ffc4d011459de557218f4f76da42aa6318fcf6e
parent3dfd4a4e657426534b132cbe0c2dbed554d060f2 (diff)
downloadoslo-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.rst1
-rw-r--r--oslo_config/cfg.py15
-rw-r--r--oslo_config/generator.py3
-rw-r--r--oslo_config/tests/test_generator.py14
-rw-r--r--oslo_config/tests/test_types.py68
-rw-r--r--oslo_config/types.py49
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