Skip to content

Commit 090be27

Browse files
authored
Add validation helper for Label Values/Keys (#164)
* Add validation helper for Label Values/Keys This implementation is similar to the implementation in the hcloud-go. To give the "user" as much as verbosity as possible i introduced two methods. - `validate` which is a simple validator that just returns True/False.add-validator-for-labels - `validate_verbose` which also returns the corresponding error message (like which key/value is not correctly formatted) I decided against calling one of both function in the other and instead duplicate the code. This is based on my feeling that the code would be more complex when we as a sample call validate_verbose in validate. Also, it is slightly more readable imho, but we can discuss this :) Closes #153 Signed-off-by: Lukas Kämmerling <lukas.kaemmerling@hetzner-cloud.de> * Fix tox Signed-off-by: Lukas Kämmerling <lukas.kaemmerling@hetzner-cloud.de> Signed-off-by: Lukas Kämmerling <lukas.kaemmerling@hetzner-cloud.de>
1 parent 76ec7ed commit 090be27

5 files changed

Lines changed: 168 additions & 3 deletions

File tree

docs/api.helpers.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Helpers
2+
==================
3+
4+
5+
.. autoclass:: hcloud.helpers.labels.LabelValidator
6+
:members:
7+

hcloud/helpers/labels.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import re
2+
from typing import Dict
3+
4+
5+
class LabelValidator:
6+
KEY_REGEX = re.compile(
7+
"^([a-z0-9A-Z]((?:[\-_.]|[a-z0-9A-Z]){0,253}[a-z0-9A-Z])?/)?[a-z0-9A-Z]((?:[\-_.]|[a-z0-9A-Z]|){0,62}[a-z0-9A-Z])?$"
8+
)
9+
VALUE_REGEX = re.compile(
10+
"^(([a-z0-9A-Z](?:[\-_.]|[a-z0-9A-Z]){0,62})?[a-z0-9A-Z]$|$)"
11+
)
12+
13+
@staticmethod
14+
def validate(labels: Dict[str, str]) -> bool:
15+
"""Validates Labels. If you want to know which key/value pair of the dict is not correctly formatted
16+
use :func:`~hcloud.helpers.labels.validate_verbose`.
17+
18+
:return: bool
19+
"""
20+
for k, v in labels.items():
21+
if LabelValidator.KEY_REGEX.match(k) is None:
22+
return False
23+
if LabelValidator.VALUE_REGEX.match(v) is None:
24+
return False
25+
return True
26+
27+
@staticmethod
28+
def validate_verbose(labels: Dict[str, str]) -> (bool, str):
29+
"""Validates Labels and returns the corresponding error message if something is wrong. Returns True, <empty string>
30+
if everything is fine.
31+
32+
:return: bool, str
33+
"""
34+
for k, v in labels.items():
35+
if LabelValidator.KEY_REGEX.match(k) is None:
36+
return False, f"label key {k} is not correctly formatted"
37+
if LabelValidator.VALUE_REGEX.match(v) is None:
38+
return False, f"label value {v} (key: {k}) is not correctly formatted"
39+
return True, ""

hcloud/load_balancers/client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ def create(
404404
public_interface=None, # type: Optional[bool]
405405
network=None, # type: Optional[Union[Network,BoundNetwork]]
406406
):
407-
# type: (...) -> CreateLoadBalancerResponse:
407+
# type: (...) -> CreateLoadBalancerResponse
408408
"""Creates a Load Balancer .
409409
410410
:param name: str

tests/unit/helpers/test_labels.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import pytest
2+
3+
from hcloud.helpers.labels import LabelValidator
4+
5+
6+
@pytest.mark.parametrize(
7+
"labels,expected",
8+
[
9+
# valid combinations
10+
({"label1": "correct.de"}, True),
11+
({"empty/label": ""}, True),
12+
({"label3-test.de/hallo.welt": "233344444443"}, True),
13+
({"label2.de/hallo": "1correct2.de"}, True),
14+
# invalid value
15+
({"valid_key": "incorrect .com"}, False),
16+
({"valid_key": "-incorrect.com"}, False),
17+
({"valid_key": "incorrect.com-"}, False),
18+
({"valid_key": "incorr,ect.com-"}, False),
19+
(
20+
{
21+
"valid_key": "incorrect-111111111111111111111111111111111111111111111111111111111111.com"
22+
},
23+
False,
24+
),
25+
# invalid keys
26+
({"incorrect.de/": "correct.de"}, False),
27+
({"incor rect.de/": "correct.de"}, False),
28+
({"incorrect.de/+": "correct.de"}, False),
29+
({"-incorrect.de": "correct.de"}, False),
30+
({"incorrect.de-": "correct.de"}, False),
31+
({"incorrect.de/tes t": "correct.de"}, False),
32+
({"incorrect.de/test-": "correct.de"}, False),
33+
(
34+
{
35+
"incorrect.de/test-dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd": "correct.de"
36+
},
37+
False,
38+
),
39+
(
40+
{
41+
"incorrect-11111111111111111111111111111111111111111111111111111111111111111111111111111111"
42+
+ "11111111111111111111111111111111111111111111111111111111111111111111111111111111"
43+
+ "11111111111111111111111111111111111111111111111111111111111111111111111111111111"
44+
+ "11111111111111111111111111111111111111111111111111111111111111111111111111111111"
45+
+ "11111111111111111111111111111111111111111111111111111111111111111111111111111111"
46+
+ "11111111111111111111111111111111111111111111111111111111111111111111111111111111"
47+
+ "11111111111111111111111111111111111111111111111111111111111111111111111111111111"
48+
+ ".de/test": "correct.de"
49+
},
50+
False,
51+
),
52+
],
53+
)
54+
def test_validate(labels, expected):
55+
assert LabelValidator.validate(labels=labels) == expected
56+
57+
58+
@pytest.mark.parametrize(
59+
"labels,expected,type",
60+
[
61+
# valid combinations
62+
({"label1": "correct.de"}, True, ""),
63+
({"empty/label": ""}, True, ""),
64+
({"label3-test.de/hallo.welt": "233344444443"}, True, ""),
65+
({"label2.de/hallo": "1correct2.de"}, True, ""),
66+
# invalid value
67+
({"valid_key": "incorrect .com"}, False, "value"),
68+
({"valid_key": "-incorrect.com"}, False, "value"),
69+
({"valid_key": "incorrect.com-"}, False, "value"),
70+
({"valid_key": "incorr,ect.com-"}, False, "value"),
71+
(
72+
{
73+
"valid_key": "incorrect-111111111111111111111111111111111111111111111111111111111111.com"
74+
},
75+
False,
76+
"value",
77+
),
78+
# invalid keys
79+
({"incorrect.de/": "correct.de"}, False, "key"),
80+
({"incor rect.de/": "correct.de"}, False, "key"),
81+
({"incorrect.de/+": "correct.de"}, False, "key"),
82+
({"-incorrect.de": "correct.de"}, False, "key"),
83+
({"incorrect.de-": "correct.de"}, False, "key"),
84+
({"incorrect.de/tes t": "correct.de"}, False, "key"),
85+
({"incorrect.de/test-": "correct.de"}, False, "key"),
86+
(
87+
{
88+
"incorrect.de/test-dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd": "correct.de"
89+
},
90+
False,
91+
"key",
92+
),
93+
(
94+
{
95+
"incorrect-11111111111111111111111111111111111111111111111111111111111111111111111111111111"
96+
+ "11111111111111111111111111111111111111111111111111111111111111111111111111111111"
97+
+ "11111111111111111111111111111111111111111111111111111111111111111111111111111111"
98+
+ "11111111111111111111111111111111111111111111111111111111111111111111111111111111"
99+
+ "11111111111111111111111111111111111111111111111111111111111111111111111111111111"
100+
+ "11111111111111111111111111111111111111111111111111111111111111111111111111111111"
101+
+ "11111111111111111111111111111111111111111111111111111111111111111111111111111111"
102+
+ ".de/test": "correct.de"
103+
},
104+
False,
105+
"key",
106+
),
107+
],
108+
)
109+
def test_validate_verbose(labels, expected, type):
110+
result, error = LabelValidator.validate_verbose(labels=labels)
111+
if type == "key" and expected is False:
112+
assert error == f"label key {list(labels.keys())[0]} is not correctly formatted"
113+
elif type == "value" and expected is False:
114+
assert (
115+
error
116+
== f"label value {list(labels.values())[0]} (key: {list(labels.keys())[0]}) is not correctly formatted"
117+
)
118+
119+
assert result == expected

tox.ini

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ envlist = py36, py37, py38, py39, py310, flake8
33

44
[testenv:flake8]
55
basepython = python
6-
deps = flake8==3.6.0
7-
commands = flake8 hcloud tests setup.py
6+
deps = flake8==5.0.4
7+
commands = flake8 --ignore F821,E501,W605,W503 hcloud tests setup.py
88

99
[testenv:black]
1010
basepython = python

0 commit comments

Comments
 (0)