66import tomllib
77from pathlib import Path
88
9- from pydantic import BaseModel , Field , field_validator
9+ from pydantic import BaseModel , Field , field_validator , model_validator
1010
1111from lib .passwords import hash_password
1212from lib .sizes import parse_size
1313from lib .typing import ConfigDict , JSONType
1414
1515from typing import cast , overload
1616
17+ logger = logging .getLogger (__name__ )
18+
1719class ConfigError (Exception ):
1820 """Raised when the TOML configuration cannot be loaded or validated."""
1921
2022
21- class _StrictModel (BaseModel ):
22- """Reject unknown keys at load time (extra="forbid")."""
23+ class WarnOnExtraModel (BaseModel ):
24+ """Drop unknown config keys, warning about them instead of failing (extra="ignore")."""
25+ model_config = {"extra" : "ignore" }
2326
24- model_config = {"extra" : "forbid" }
27+ @model_validator (mode = "before" )
28+ @classmethod
29+ def _warn_unknown_fields (cls , data : object ) -> object :
30+ if not isinstance (data , dict ):
31+ return data
32+ if cls .model_config .get ("extra" ) == "allow" :
33+ return data
34+ expected = set (cls .model_fields ) | {
35+ f .alias for f in cls .model_fields .values () if f .alias
36+ }
37+ unknown = set (data ) - expected
38+ if unknown :
39+ logger .warning (
40+ "[%s] Unknown config key(s) ignored (config may be from a newer project version): %s" ,
41+ cls .__name__ , ", " .join (sorted (unknown )),
42+ )
43+ return data
2544
2645
2746REPO_ROOT = Path (__file__ ).resolve ().parent .parent
2847
2948
30- class HostConfig (_StrictModel ):
49+ class HostConfig (WarnOnExtraModel ):
3150 default_user : str
3251 default_password : str
3352 default_password_hash : str = ""
3453
3554
36- class HostOverride (_StrictModel ):
55+ class HostOverride (WarnOnExtraModel ):
3756 user : str | None = None
3857 password : str | None = None
3958 skip_xo_config : bool | None = None
@@ -42,25 +61,25 @@ class HostOverride(_StrictModel):
4261 hosting_pool : str | None = None
4362
4463
45- class NetworkConfig (_StrictModel ):
64+ class NetworkConfig (WarnOnExtraModel ):
4665 mgmt : str
4766 free_nics : list [str ]
4867
4968
50- class PXEConfig (_StrictModel ):
69+ class PXEConfig (WarnOnExtraModel ):
5170 config_server : str
5271 arp_server : str
5372
5473
55- class VMConfig (_StrictModel ):
74+ class VMConfig (WarnOnExtraModel ):
5675 def_url : str
5776 cache_imported : bool
5877 default_sr : str
5978 images : dict [str , str ]
6079 equivalents : dict [str , str ]
6180
6281
63- class IsoImageDef (_StrictModel ):
82+ class IsoImageDef (WarnOnExtraModel ):
6483 path : str
6584 net_url : str | None = Field (default = None , alias = "net-url" )
6685 net_only : bool | None = Field (default = None , alias = "net-only" )
@@ -69,26 +88,26 @@ class IsoImageDef(_StrictModel):
6988 model_config = {"populate_by_name" : True }
7089
7190
72- class InstallIsosConfig (_StrictModel ):
91+ class InstallIsosConfig (WarnOnExtraModel ):
7392 base_url : str
7493 cache_dir : str
7594 definitions : dict [str , IsoImageDef ]
7695
7796
78- class AnswerFileDef (_StrictModel ):
97+ class AnswerFileDef (WarnOnExtraModel ):
7998 model_config = {"extra" : "allow" }
8099
81100 TAG : str
82101 CONTENTS : str | list [AnswerFileDef ] | None = None
83102
84103
85- class InstallConfig (_StrictModel ):
104+ class InstallConfig (WarnOnExtraModel ):
86105 answerfiles : dict [str , AnswerFileDef ]
87106 isos : InstallIsosConfig
88107 iso_remaster : str = ""
89108
90109
91- class WinGuestToolDef (_StrictModel ):
110+ class WinGuestToolDef (WarnOnExtraModel ):
92111 name : str
93112 download : bool
94113 package : str
@@ -97,12 +116,12 @@ class WinGuestToolDef(_StrictModel):
97116 onboard_family : str | None = None
98117
99118
100- class OtherGuestToolDef (_StrictModel ):
119+ class OtherGuestToolDef (WarnOnExtraModel ):
101120 name : str
102121 download : bool
103122
104123
105- class InstalledGuestToolDef (_StrictModel ):
124+ class InstalledGuestToolDef (WarnOnExtraModel ):
106125 type : str | None = None
107126 path : str | None = None
108127 package : str | None = None
@@ -112,74 +131,74 @@ class InstalledGuestToolDef(_StrictModel):
112131 onboarding_phase : str | None = None
113132
114133
115- class GuestToolsConfig (_StrictModel ):
134+ class GuestToolsConfig (WarnOnExtraModel ):
116135 download_url : str
117136 win : dict [str , WinGuestToolDef ]
118137 other : OtherGuestToolDef
119138 installed : dict [str , InstalledGuestToolDef ]
120139
121140
122- class XOConfig (_StrictModel ):
141+ class XOConfig (WarnOnExtraModel ):
123142 cli : str
124143
125144
126- class SSHConfig (_StrictModel ):
145+ class SSHConfig (WarnOnExtraModel ):
127146 pubkey : str
128147 output_max_lines : int
129148 ignore_banner : bool
130149
131150
132- class LinstorConfig (_StrictModel ):
151+ class LinstorConfig (WarnOnExtraModel ):
133152 redundancy : int
134153
135154
136- class NFSConfig (_StrictModel ):
155+ class NFSConfig (WarnOnExtraModel ):
137156 server : str | None = None
138157 serverpath : str | None = None
139158
140159
141- class NFS4Config (_StrictModel ):
160+ class NFS4Config (WarnOnExtraModel ):
142161 server : str | None = None
143162 serverpath : str | None = None
144163 nfsversion : str | None = None
145164
146165
147- class NFSISOConfig (_StrictModel ):
166+ class NFSISOConfig (WarnOnExtraModel ):
148167 location : str | None = None
149168
150169
151- class CIFSISOConfig (_StrictModel ):
170+ class CIFSISOConfig (WarnOnExtraModel ):
152171 location : str | None = None
153172 username : str | None = None
154173 cifspassword : str | None = None
155174 type : str | None = None
156175 vers : str | None = None
157176
158177
159- class CephFSConfig (_StrictModel ):
178+ class CephFSConfig (WarnOnExtraModel ):
160179 server : str | None = None
161180 serverpath : str | None = None
162181 options : str | None = None
163182
164183
165- class MooseFSConfig (_StrictModel ):
184+ class MooseFSConfig (WarnOnExtraModel ):
166185 masterhost : str | None = None
167186 masterport : str | None = None
168187 rootpath : str | None = None
169188
170189
171- class LVMoHBAConfig (_StrictModel ):
190+ class LVMoHBAConfig (WarnOnExtraModel ):
172191 SCSIid : str | None = None
173192
174193
175- class LVMoISCSIConfig (_StrictModel ):
194+ class LVMoISCSIConfig (WarnOnExtraModel ):
176195 target : str | None = None
177196 port : str | None = None
178197 targetIQN : str | None = None
179198 SCSIid : str | None = None
180199
181200
182- class StorageConfig (_StrictModel ):
201+ class StorageConfig (WarnOnExtraModel ):
183202 nfs : NFSConfig
184203 nfs4 : NFS4Config
185204 nfs_iso : NFSISOConfig
@@ -191,17 +210,17 @@ class StorageConfig(_StrictModel):
191210 linstor : LinstorConfig
192211
193212
194- class UpdateDefaults (_StrictModel ):
213+ class UpdateDefaults (WarnOnExtraModel ):
195214 repositories : list [str ] = Field (default_factory = list )
196215 disabled_repositories : list [str ] = Field (default_factory = list )
197216 hosting_pool : str | None = None
198217
199218
200- class ToolsConfig (_StrictModel ):
219+ class ToolsConfig (WarnOnExtraModel ):
201220 update : UpdateDefaults = Field (default_factory = UpdateDefaults )
202221
203222
204- class Config (_StrictModel ):
223+ class Config (WarnOnExtraModel ):
205224 objects_name_prefix : str | None
206225 dns_server : str
207226 host : HostConfig
0 commit comments