2626WINDOWS_OEM_VOLUME_NAME = "opensandbox-win-oem"
2727WINDOWS_KVM_VOLUME_NAME = "opensandbox-win-kvm"
2828WINDOWS_TUN_VOLUME_NAME = "opensandbox-win-tun"
29+ WINDOWS_PROFILE_DEFAULT_USER_PORTS = ["44772" , "8080" , "3389/tcp" , "3389/udp" , "8006/tcp" ]
2930
3031
3132def is_windows_profile (platform : Optional [PlatformSpec ]) -> bool :
@@ -39,12 +40,10 @@ def validate_windows_profile_resource_limits(resource_limits: dict[str, str]) ->
3940def build_windows_profile_env (
4041 env : dict [str , str ],
4142 resource_limits : dict [str , str ],
42- exposed_ports : Optional [list [str ]] = None ,
4343) -> list [dict [str , str ]]:
4444 env_items = [f"{ key } ={ value } " for key , value in env .items ()]
4545 env_items = inject_windows_resource_limits_env (env_items , resource_limits or {})
46- if exposed_ports is not None :
47- env_items = inject_windows_user_ports (env_items , exposed_ports )
46+ env_items = inject_windows_user_ports (env_items , WINDOWS_PROFILE_DEFAULT_USER_PORTS )
4847
4948 result : list [dict [str , str ]] = []
5049 for item in env_items :
@@ -130,6 +129,46 @@ def apply_windows_profile_overrides(
130129 )
131130
132131
132+ def apply_windows_profile_arch_selector (
133+ pod_spec : Dict [str , Any ],
134+ template_spec : Dict [str , Any ],
135+ platform : Optional [PlatformSpec ],
136+ ) -> None :
137+ """
138+ Apply platform.arch constraint for windows profile pods.
139+
140+ We intentionally avoid forcing kubernetes.io/os=windows for this profile,
141+ but still honor arch constraints from API requests and fail early on
142+ template conflicts.
143+ """
144+ if platform is None :
145+ return
146+
147+ requested_arch = platform .arch
148+ template_selector = template_spec .get ("nodeSelector" , {})
149+ if not isinstance (template_selector , dict ):
150+ template_selector = {}
151+
152+ existing_arch = template_selector .get ("kubernetes.io/arch" )
153+ if existing_arch is not None and existing_arch != requested_arch :
154+ raise ValueError (
155+ "platform conflict with template nodeSelector: 'kubernetes.io/arch' "
156+ f"is '{ existing_arch } ', request expects '{ requested_arch } '."
157+ )
158+
159+ if not _template_allows_arch (template_spec , requested_arch ):
160+ raise ValueError (
161+ "platform conflict with template nodeAffinity: required node affinity "
162+ f"does not allow requested architecture '{ requested_arch } '."
163+ )
164+
165+ node_selector = pod_spec .setdefault ("nodeSelector" , {})
166+ if not isinstance (node_selector , dict ):
167+ node_selector = {}
168+ pod_spec ["nodeSelector" ] = node_selector
169+ node_selector ["kubernetes.io/arch" ] = requested_arch
170+
171+
133172def _merge_volume_mounts (container : Dict [str , Any ], mounts_to_add : List [Dict [str , str ]]) -> None :
134173 mounts = container .setdefault ("volumeMounts" , [])
135174 if not isinstance (mounts , list ):
@@ -156,3 +195,48 @@ def _merge_volumes(pod_spec: Dict[str, Any], volumes_to_add: List[Dict[str, Any]
156195 continue
157196 volumes .append (volume )
158197 existing_names .add (name )
198+
199+
200+ def _template_allows_arch (template_spec : Dict [str , Any ], requested_arch : str ) -> bool :
201+ affinity = template_spec .get ("affinity" , {})
202+ if not isinstance (affinity , dict ):
203+ return True
204+
205+ node_affinity = affinity .get ("nodeAffinity" , {})
206+ if not isinstance (node_affinity , dict ):
207+ return True
208+
209+ required = node_affinity .get ("requiredDuringSchedulingIgnoredDuringExecution" , {})
210+ if not isinstance (required , dict ):
211+ return True
212+
213+ terms = required .get ("nodeSelectorTerms" , [])
214+ if not isinstance (terms , list ) or not terms :
215+ return True
216+
217+ return any (_arch_term_satisfiable (term , requested_arch ) for term in terms if isinstance (term , dict ))
218+
219+
220+ def _arch_term_satisfiable (term : Dict [str , Any ], requested_arch : str ) -> bool :
221+ expressions = term .get ("matchExpressions" , [])
222+ if not isinstance (expressions , list ):
223+ return True
224+
225+ for expr in expressions :
226+ if not isinstance (expr , dict ):
227+ continue
228+ if expr .get ("key" ) != "kubernetes.io/arch" :
229+ continue
230+ operator = expr .get ("operator" )
231+ values = expr .get ("values" , [])
232+ if not isinstance (values , list ):
233+ values = []
234+
235+ if operator == "In" and requested_arch not in values :
236+ return False
237+ if operator == "NotIn" and requested_arch in values :
238+ return False
239+ if operator == "DoesNotExist" :
240+ return False
241+
242+ return True
0 commit comments