44
44
ADDC : typing .Final [str ] = "addc"
45
45
FEATURES : typing .Final [str ] = "instance_features"
46
46
47
+ # cache for json schema data
48
+ _JSON_SCHEMA : dict [str , typing .Any ] = {}
47
49
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 :
49
99
"""Read the global container config from the given filenames.
50
100
At least one of the files from the fnames list must exist and contain
51
101
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:
60
110
for fname in fnames :
61
111
try :
62
112
with _open (fname ) as fh :
63
- gconfig .load (fh )
113
+ gconfig .load (fh , require_validation = require_validation )
64
114
readfiles .add (fname )
65
115
except OSError as err :
66
116
if getattr (err , "errno" , 0 ) != errno .ENOENT :
67
117
raise
68
118
if not readfiles :
69
119
# we read nothing! don't proceed
70
120
raise ValueError (f"None of the config file paths exist: { fnames } " )
71
- # Verify that we loaded something
72
- _check_config_data (gconfig .data )
73
121
return gconfig
74
122
75
123
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
-
89
124
class SambaConfig (typing .Protocol ):
90
125
def global_options (self ) -> typing .Iterable [typing .Tuple [str , str ]]:
91
126
... # pragma: no cover
@@ -105,8 +140,15 @@ def __init__(
105
140
if source is not None :
106
141
self .load (source )
107
142
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
+ )
110
152
self .data .update (data )
111
153
112
154
def get (self , ident : str ) -> InstanceConfig :
0 commit comments