11from __future__ import annotations
22
33import argparse
4+ import logging
45import os
56import tomllib
67from pathlib import Path
78
8- from pydantic import BaseModel , Field , field_validator
9+ from pydantic import BaseModel , Field , field_validator , model_validator
910
1011from lib .passwords import hash_password
1112from lib .sizes import parse_size
1213from lib .typing import ConfigDict , JSONType
1314
1415from typing import overload
1516
17+ logger = logging .getLogger (__name__ )
18+
1619class ConfigError (Exception ):
1720 """Raised when the TOML configuration cannot be loaded or validated."""
1821
1922
20- class _StrictModel (BaseModel ):
21- """Reject unknown keys at load time (extra="forbid")."""
22- model_config = {"extra" : "forbid" }
23+ class WarnOnExtraModel (BaseModel ):
24+ """Drop unknown config keys, warning about them instead of failing (extra="ignore")."""
25+ model_config = {"extra" : "ignore" }
26+
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
2344
2445
2546REPO_ROOT = Path (__file__ ).resolve ().parent .parent
2647
2748
28- class HostConfig (_StrictModel ):
49+ class HostConfig (WarnOnExtraModel ):
2950 default_user : str
3051 default_password : str
3152 default_password_hash : str = ""
3253
3354
34- class HostOverride (_StrictModel ):
55+ class HostOverride (WarnOnExtraModel ):
3556 user : str | None = None
3657 password : str | None = None
3758 skip_xo_config : bool | None = None
@@ -40,25 +61,25 @@ class HostOverride(_StrictModel):
4061 hosting_pool : str | None = None
4162
4263
43- class NetworkConfig (_StrictModel ):
64+ class NetworkConfig (WarnOnExtraModel ):
4465 mgmt : str
4566 free_nics : list [str ]
4667
4768
48- class PXEConfig (_StrictModel ):
69+ class PXEConfig (WarnOnExtraModel ):
4970 config_server : str
5071 arp_server : str
5172
5273
53- class VMConfig (_StrictModel ):
74+ class VMConfig (WarnOnExtraModel ):
5475 def_url : str
5576 cache_imported : bool
5677 default_sr : str
5778 images : dict [str , str ]
5879 equivalents : dict [str , str ]
5980
6081
61- class IsoImageDef (_StrictModel ):
82+ class IsoImageDef (WarnOnExtraModel ):
6283 path : str
6384 net_url : str | None = Field (default = None , alias = "net-url" )
6485 net_only : bool | None = Field (default = None , alias = "net-only" )
@@ -67,26 +88,26 @@ class IsoImageDef(_StrictModel):
6788 model_config = {"populate_by_name" : True }
6889
6990
70- class InstallIsosConfig (_StrictModel ):
91+ class InstallIsosConfig (WarnOnExtraModel ):
7192 base_url : str
7293 cache_dir : str
7394 definitions : dict [str , IsoImageDef ]
7495
7596
76- class AnswerFileDef (_StrictModel ):
97+ class AnswerFileDef (WarnOnExtraModel ):
7798 model_config = {"extra" : "allow" }
7899
79100 TAG : str
80101 CONTENTS : str | list [AnswerFileDef ] | None = None
81102
82103
83- class InstallConfig (_StrictModel ):
104+ class InstallConfig (WarnOnExtraModel ):
84105 answerfiles : dict [str , AnswerFileDef ]
85106 isos : InstallIsosConfig
86107 iso_remaster : str = ""
87108
88109
89- class WinGuestToolDef (_StrictModel ):
110+ class WinGuestToolDef (WarnOnExtraModel ):
90111 name : str
91112 download : bool
92113 package : str
@@ -95,12 +116,12 @@ class WinGuestToolDef(_StrictModel):
95116 onboard_family : str | None = None
96117
97118
98- class OtherGuestToolDef (_StrictModel ):
119+ class OtherGuestToolDef (WarnOnExtraModel ):
99120 name : str
100121 download : bool
101122
102123
103- class InstalledGuestToolDef (_StrictModel ):
124+ class InstalledGuestToolDef (WarnOnExtraModel ):
104125 type : str | None = None
105126 path : str | None = None
106127 package : str | None = None
@@ -110,74 +131,74 @@ class InstalledGuestToolDef(_StrictModel):
110131 onboarding_phase : str | None = None
111132
112133
113- class GuestToolsConfig (_StrictModel ):
134+ class GuestToolsConfig (WarnOnExtraModel ):
114135 download_url : str
115136 win : dict [str , WinGuestToolDef ]
116137 other : OtherGuestToolDef
117138 installed : dict [str , InstalledGuestToolDef ]
118139
119140
120- class XOConfig (_StrictModel ):
141+ class XOConfig (WarnOnExtraModel ):
121142 cli : str
122143
123144
124- class SSHConfig (_StrictModel ):
145+ class SSHConfig (WarnOnExtraModel ):
125146 pubkey : str
126147 output_max_lines : int
127148 ignore_banner : bool
128149
129150
130- class LinstorConfig (_StrictModel ):
151+ class LinstorConfig (WarnOnExtraModel ):
131152 redundancy : int
132153
133154
134- class NFSConfig (_StrictModel ):
155+ class NFSConfig (WarnOnExtraModel ):
135156 server : str | None = None
136157 serverpath : str | None = None
137158
138159
139- class NFS4Config (_StrictModel ):
160+ class NFS4Config (WarnOnExtraModel ):
140161 server : str | None = None
141162 serverpath : str | None = None
142163 nfsversion : str | None = None
143164
144165
145- class NFSISOConfig (_StrictModel ):
166+ class NFSISOConfig (WarnOnExtraModel ):
146167 location : str | None = None
147168
148169
149- class CIFSISOConfig (_StrictModel ):
170+ class CIFSISOConfig (WarnOnExtraModel ):
150171 location : str | None = None
151172 username : str | None = None
152173 cifspassword : str | None = None
153174 type : str | None = None
154175 vers : str | None = None
155176
156177
157- class CephFSConfig (_StrictModel ):
178+ class CephFSConfig (WarnOnExtraModel ):
158179 server : str | None = None
159180 serverpath : str | None = None
160181 options : str | None = None
161182
162183
163- class MooseFSConfig (_StrictModel ):
184+ class MooseFSConfig (WarnOnExtraModel ):
164185 masterhost : str | None = None
165186 masterport : str | None = None
166187 rootpath : str | None = None
167188
168189
169- class LVMoHBAConfig (_StrictModel ):
190+ class LVMoHBAConfig (WarnOnExtraModel ):
170191 SCSIid : str | None = None
171192
172193
173- class LVMoISCSIConfig (_StrictModel ):
194+ class LVMoISCSIConfig (WarnOnExtraModel ):
174195 target : str | None = None
175196 port : str | None = None
176197 targetIQN : str | None = None
177198 SCSIid : str | None = None
178199
179200
180- class StorageConfig (_StrictModel ):
201+ class StorageConfig (WarnOnExtraModel ):
181202 nfs : NFSConfig
182203 nfs4 : NFS4Config
183204 nfs_iso : NFSISOConfig
@@ -189,17 +210,17 @@ class StorageConfig(_StrictModel):
189210 linstor : LinstorConfig
190211
191212
192- class UpdateDefaults (_StrictModel ):
213+ class UpdateDefaults (WarnOnExtraModel ):
193214 repositories : list [str ] = Field (default_factory = list )
194215 disabled_repositories : list [str ] = Field (default_factory = list )
195216 hosting_pool : str | None = None
196217
197218
198- class ToolsConfig (_StrictModel ):
219+ class ToolsConfig (WarnOnExtraModel ):
199220 update : UpdateDefaults = Field (default_factory = UpdateDefaults )
200221
201222
202- class Config (_StrictModel ):
223+ class Config (WarnOnExtraModel ):
203224 objects_name_prefix : str | None
204225 dns_server : str
205226 host : HostConfig
0 commit comments