55
66from typing import List , Union
77
8+ from functools import reduce
9+ from copy import deepcopy
810from azure .ai .ml ._restclient .v2022_10_01_preview .models import (
911 AcrDetails as RestAcrDetails ,
1012 ArmResourceId as RestArmResourceId ,
1113 RegistryRegionArmDetails as RestRegistryRegionArmDetails ,
1214 StorageAccountDetails as RestStorageAccountDetails ,
1315 SystemCreatedAcrAccount as RestSystemCreatedAcrAccount ,
1416 SystemCreatedStorageAccount as RestSystemCreatedStorageAccount ,
15- UserCreatedAcrAccount as RestUserCreatedAcrAccount ,
16- UserCreatedStorageAccount as RestUserCreatedStorageAccount )
17+ UserCreatedAcrAccount as RestUserCreatedAcrAccount )
1718from azure .ai .ml ._utils ._experimental import experimental
1819from azure .ai .ml .constants ._registry import StorageAccountType
19-
20+ from azure .ai .ml .exceptions import ErrorCategory , ErrorTarget , ValidationErrorType , ValidationException
21+ from azure .ai .ml ._exception_helper import log_and_raise_error
22+ from .util import _make_rest_user_storage_from_id
2023
2124# This exists despite not being used by the schema validator because this entire
2225# class is an output only value from the API.
@@ -93,6 +96,8 @@ def __init__(
9396 storage_account_hns : bool ,
9497 storage_account_type : StorageAccountType ,
9598 arm_resource_id : str = None ,
99+ replicated_ids : List [str ] = None ,
100+ replication_count = 1 ,
96101 ):
97102 """
98103 :param arm_resource_id: Resource ID of the storage account.
@@ -104,58 +109,19 @@ def __init__(
104109 "Standard_GRS, "Standard_RAGRS", "Standard_ZRS", "Standard_GZRS",
105110 "Standard_RAGZRS", "Premium_LRS", "Premium_ZRS"
106111 :type storage_account_type: StorageAccountType
112+ :param replication_count: The number of replicas of this storage account
113+ that should be created. Defaults to 1. Values less than 1 are invalid.
114+ :type replication_count: int
115+ :param replicated_ids: If this storage was replicated, then this is a
116+ list of all storage IDs with these settings for this registry.
117+ Defaults to none for un-replicated storage accounts.
118+ :type replicated_ids: List[str]
107119 """
108120 self .arm_resource_id = arm_resource_id
109121 self .storage_account_hns = storage_account_hns
110122 self .storage_account_type = storage_account_type
111-
112- # storage should technically be a union between str and SystemCreatedStorageAccount,
113- # but python doesn't accept self class references apparently.
114- # Class method instead of normal function to accept possible
115- # string input.
116-
117- @classmethod
118- def _to_rest_object (cls , storage ) -> RestStorageAccountDetails :
119- if hasattr (storage , "storage_account_type" ) and storage .storage_account_type is not None :
120-
121- # We DO NOT want to set the arm_resource_id. The backend provides very
122- # unhelpful errors if you provide an empty/null/invalid resource ID,
123- # and ignores the value otherwise. It's better to avoid setting it in
124- # the conversion in this direction at all.
125- # We don't bother processing storage_account_type because the
126- # rest version is case insensitive.
127- account = RestSystemCreatedStorageAccount (
128- storage_account_hns_enabled = storage .storage_account_hns ,
129- storage_account_type = storage .storage_account_type ,
130- )
131- return RestStorageAccountDetails (system_created_storage_account = account )
132- else :
133- return RestStorageAccountDetails (
134- user_created_storage_account = RestUserCreatedStorageAccount (
135- arm_resource_id = RestArmResourceId (resource_id = storage )
136- )
137- )
138-
139- @classmethod
140- def _from_rest_object (cls , rest_obj : RestStorageAccountDetails ) -> "Union[str, SystemCreatedStorageAccount]" :
141- if not rest_obj :
142- return None
143- # TODO should we even bother check if both values are set and throw an error? This shouldn't be possible.
144- if rest_obj .system_created_storage_account :
145- resource_id = None
146- if rest_obj .system_created_storage_account .arm_resource_id :
147- resource_id = rest_obj .system_created_storage_account .arm_resource_id .resource_id
148- return SystemCreatedStorageAccount (
149- storage_account_hns = rest_obj .system_created_storage_account .storage_account_hns_enabled ,
150- storage_account_type = StorageAccountType (
151- rest_obj .system_created_storage_account .storage_account_type .lower ()
152- ), # TODO validate storage account type?
153- arm_resource_id = resource_id ,
154- )
155- elif rest_obj .user_created_storage_account :
156- return rest_obj .user_created_storage_account .arm_resource_id .resource_id
157- else :
158- return None # TODO should this throw an error instead?
123+ self .replication_count = replication_count
124+ self .replicated_ids = replicated_ids
159125
160126
161127# Per-region information for registries.
@@ -166,7 +132,7 @@ def __init__(
166132 * ,
167133 acr_config : List [Union [str , SystemCreatedAcrAccount ]] = None ,
168134 location : str = None ,
169- storage_config : List [ Union [str , SystemCreatedStorageAccount ] ] = None ,
135+ storage_config : Union [List [ str ] , SystemCreatedStorageAccount ] = None ,
170136 ):
171137 """
172138 Details for each region a registry is in.
@@ -197,10 +163,12 @@ def _from_rest_object(cls, rest_obj: RestRegistryRegionArmDetails) -> "RegistryR
197163 acr ) for acr in rest_obj .acr_details ]
198164 storages = []
199165 if rest_obj .storage_account_details :
200- storages = [ SystemCreatedStorageAccount . _from_rest_object (
201- storages ) for storages in rest_obj . storage_account_details ]
166+ storages = cls . _storage_config_from_rest_object ( rest_obj . storage_account_details )
167+
202168 return RegistryRegionDetails (
203- acr_config = converted_acr_details , location = rest_obj .location , storage_config = storages
169+ acr_config = converted_acr_details ,
170+ location = rest_obj .location ,
171+ storage_config = storages
204172 )
205173
206174 def _to_rest_object (self ) -> RestRegistryRegionArmDetails :
@@ -210,10 +178,91 @@ def _to_rest_object(self) -> RestRegistryRegionArmDetails:
210178 acr ) for acr in self .acr_config ]
211179 storages = []
212180 if self .storage_config :
213- storages = [SystemCreatedStorageAccount ._to_rest_object (
214- storage ) for storage in self .storage_config ]
181+ storages = self ._storage_config_to_rest_object ()
215182 return RestRegistryRegionArmDetails (
216183 acr_details = converted_acr_details ,
217184 location = self .location ,
218185 storage_account_details = storages ,
219186 )
187+
188+
189+ def _storage_config_to_rest_object (self ) -> List [RestStorageAccountDetails ]:
190+ storage = self .storage_config
191+ # storage_config can either be a single system-created storage account,
192+ # or list of user-inputted id's.
193+ if (storage is not None
194+ and hasattr (storage , "storage_account_type" )
195+ and storage .storage_account_type is not None ):
196+
197+ # We DO NOT want to set the arm_resource_id. The backend provides very
198+ # unhelpful errors if you provide an empty/null/invalid resource ID,
199+ # and ignores the value otherwise. It's better to avoid setting it in
200+ # the conversion in this direction at all.
201+ # We don't bother processing storage_account_type because the
202+ # rest version is case insensitive.
203+ account = RestStorageAccountDetails (system_created_storage_account = RestSystemCreatedStorageAccount (
204+ storage_account_hns_enabled = storage .storage_account_hns ,
205+ storage_account_type = storage .storage_account_type ,
206+ ))
207+ # duplicate this value based on the replication_count
208+ count = storage .replication_count
209+ if count < 1 :
210+ raise ValueError (f"Replication count cannot be less than 1. Value was: { count } ." )
211+ return [deepcopy (account ) for _ in range (0 , count )]
212+ elif storage is not None and len (storage ) > 0 :
213+ return [_make_rest_user_storage_from_id (user_id = user_id ) for user_id in storage ]
214+ else :
215+ return []
216+
217+ @classmethod
218+ def _storage_config_from_rest_object (
219+ cls ,
220+ rest_configs : List [RestStorageAccountDetails ]
221+ ) -> Union [List [str ], SystemCreatedStorageAccount ]:
222+ if not rest_configs :
223+ return None
224+ num_configs = len (rest_configs )
225+ if num_configs == 0 :
226+ return None
227+ system_created_count = reduce (lambda x , y : int (x ) + int (y ),
228+ [hasattr (config , "system_created_storage_account" ) and config .system_created_storage_account is not None
229+ for config in rest_configs ])
230+ # configs should be mono-typed. Either they're all system created
231+ # or all user created.
232+ if system_created_count == num_configs :
233+ # System created case - assume all elements are duplicates
234+ # of a single storage configuration.
235+ # Convert back into a single local representation by
236+ # combining id's into a list, and using the first element's
237+ # account type and hns.
238+ first_config = rest_configs [0 ].system_created_storage_account
239+ resource_id = None
240+ if first_config .arm_resource_id :
241+ resource_id = first_config .arm_resource_id .resource_id
242+ # account for ids of duplicated if they exist
243+ replicated_ids = None
244+ if num_configs > 1 :
245+ replicated_ids = [config .system_created_storage_account .arm_resource_id .resource_id
246+ for config in rest_configs ]
247+ return SystemCreatedStorageAccount (
248+ storage_account_hns = first_config .storage_account_hns_enabled ,
249+ storage_account_type = StorageAccountType (
250+ first_config .storage_account_type .lower ()
251+ ), # TODO validate storage account type? GI
252+ arm_resource_id = resource_id ,
253+ replication_count = num_configs ,
254+ replicated_ids = replicated_ids
255+ )
256+ elif system_created_count == 0 :
257+ return [config .user_created_storage_account .arm_resource_id .resource_id for config in rest_configs ]
258+ else :
259+ msg = f'''tried reading in a registry whose storage accounts were not
260+ mono-managed or user-created. { system_created_count } out of { num_configs } were managed.'''
261+ err = ValidationException (
262+ message = msg ,
263+ target = ErrorTarget .REGISTRY ,
264+ no_personal_data_message = msg ,
265+ error_category = ErrorCategory .USER_ERROR ,
266+ error_type = ValidationErrorType .INVALID_VALUE ,
267+ )
268+ log_and_raise_error (err )
0 commit comments