Skip to content

Commit 6caa92e

Browse files
sambacc: add support for json schema based validation of the config
This uses the jsonschema library to check that the structure of the JSON config matches the supplied schema. Signed-off-by: John Mulligan <[email protected]>
1 parent 711a87d commit 6caa92e

File tree

2 files changed

+62
-19
lines changed

2 files changed

+62
-19
lines changed

sambacc/config.py

Lines changed: 61 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,58 @@
4444
ADDC: typing.Final[str] = "addc"
4545
FEATURES: typing.Final[str] = "instance_features"
4646

47+
# cache for json schema data
48+
_JSON_SCHEMA: dict[str, typing.Any] = {}
4749

48-
def read_config_files(fnames: list[str]) -> GlobalConfig:
50+
51+
class ValidationUnsupported(Exception):
52+
pass
53+
54+
55+
def _schema_validate(data: dict[str, typing.Any], version: str) -> None:
56+
try:
57+
import jsonschema
58+
except ImportError:
59+
raise ValidationUnsupported()
60+
61+
global _JSON_SCHEMA
62+
if version == "v0" and version not in _JSON_SCHEMA:
63+
try:
64+
import sambacc.schema.conf_v0_schema
65+
66+
_JSON_SCHEMA[version] = sambacc.schema.conf_v0_schema.SCHEMA
67+
except ImportError:
68+
raise ValidationUnsupported()
69+
jsonschema.validate(instance=data, schema=_JSON_SCHEMA[version])
70+
71+
72+
def _check_config_version(data: JSONData) -> str:
73+
"""Return the config data or raise a ValueError if the config
74+
is invalid or incomplete.
75+
"""
76+
# short-cut to validate that this is something we want to consume
77+
version = data.get("samba-container-config")
78+
if version is None:
79+
raise ValueError("Invalid config: no samba-container-config key")
80+
elif version not in _VALID_VERSIONS:
81+
raise ValueError(f"Invalid config: unknown version {version}")
82+
return version
83+
84+
85+
def _check_config_valid(
86+
data: JSONData, version: str, required: typing.Optional[bool] = None
87+
) -> None:
88+
if required or required is None:
89+
try:
90+
_schema_validate(data, version)
91+
except ValidationUnsupported:
92+
if required:
93+
raise
94+
95+
96+
def read_config_files(
97+
fnames: list[str], *, require_validation: typing.Optional[bool] = None
98+
) -> GlobalConfig:
4999
"""Read the global container config from the given filenames.
50100
At least one of the files from the fnames list must exist and contain
51101
a valid config. If none of the file names exist an error will be raised.
@@ -60,32 +110,17 @@ def read_config_files(fnames: list[str]) -> GlobalConfig:
60110
for fname in fnames:
61111
try:
62112
with _open(fname) as fh:
63-
gconfig.load(fh)
113+
gconfig.load(fh, require_validation=require_validation)
64114
readfiles.add(fname)
65115
except OSError as err:
66116
if getattr(err, "errno", 0) != errno.ENOENT:
67117
raise
68118
if not readfiles:
69119
# we read nothing! don't proceed
70120
raise ValueError(f"None of the config file paths exist: {fnames}")
71-
# Verify that we loaded something
72-
_check_config_data(gconfig.data)
73121
return gconfig
74122

75123

76-
def _check_config_data(data: JSONData) -> JSONData:
77-
"""Return the config data or raise a ValueError if the config
78-
is invalid or incomplete.
79-
"""
80-
# short-cut to validate that this is something we want to consume
81-
version = data.get("samba-container-config")
82-
if version is None:
83-
raise ValueError("Invalid config: no samba-container-config key")
84-
elif version not in _VALID_VERSIONS:
85-
raise ValueError(f"Invalid config: unknown version {version}")
86-
return data
87-
88-
89124
class SambaConfig(typing.Protocol):
90125
def global_options(self) -> typing.Iterable[typing.Tuple[str, str]]:
91126
... # pragma: no cover
@@ -105,8 +140,15 @@ def __init__(
105140
if source is not None:
106141
self.load(source)
107142

108-
def load(self, source: typing.IO) -> None:
109-
data = _check_config_data(json.load(source))
143+
def load(
144+
self,
145+
source: typing.IO,
146+
require_validation: typing.Optional[bool] = None,
147+
) -> None:
148+
data = json.load(source)
149+
_check_config_valid(
150+
data, _check_config_version(data), require_validation
151+
)
110152
self.data.update(data)
111153

112154
def get(self, ident: str) -> InstanceConfig:

tox.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ deps =
1212
pytest
1313
pytest-cov
1414
dnspython
15+
jsonschema
1516
commands =
1617
py.test -v tests --cov=sambacc --cov-report=html {posargs}
1718

0 commit comments

Comments
 (0)