44
55from .base_command import BaseCommand
66from . import query , devices
7- from .enum import ListFilter , PolicyType
7+ from .enum import Duration , ListFilter , PolicyType
88from .types import ComplianceSettingsBuilder , ExtendedAttributesBuilder
99from ..common import union , Object
1010from ..exceptions import CTERAException , ObjectNotFoundException
1111
1212
1313logger = logging .getLogger ('cterasdk.core' )
1414
15+ # Suggested ``include`` paths when listing or fetching Fusion Direct cloud folders.
16+ # Portal response fields keep legacy names ``openStorageEnabled``, ``openFabricSettings``, etc.
17+ CLOUD_DRIVE_FUSION_DIRECT_INCLUDE = [
18+ 'openStorageEnabled' ,
19+ 'openFabricSettings' ,
20+ 'openFabricStorageStatus' ,
21+ 'openFabricStorageStatusDetails' ,
22+ ]
23+ CLOUD_DRIVE_OPEN_FABRIC_INCLUDE = CLOUD_DRIVE_FUSION_DIRECT_INCLUDE # legacy alias
24+
25+
26+ def _worm_period (amount , duration_type ):
27+ period = Object ()
28+ period ._classname = 'WormPeriod' # pylint: disable=protected-access
29+ period .amount = amount
30+ period .type = duration_type
31+ return period
32+
33+
34+ def _default_archive_settings ():
35+ """Defaults aligned with portal ``CloudDriveCreateParams`` / Java ``AdminApiOperations.createCloudFolderParams``.
36+
37+ Used when :meth:`CloudDrives.add` / :meth:`CloudDrives.add_return_id` are called without ``archive_settings``.
38+ """
39+ arch = Object ()
40+ arch ._classname = 'ArchiveSettings' # pylint: disable=protected-access
41+ arch .archive = False
42+ arch .gracePeriod = _worm_period (0 , Duration .Minutes )
43+ arch .retentionMode = 'Enterprise'
44+ arch .retentionPeriod = _worm_period (30 , Duration .Days )
45+ arch .deleteData = False
46+ arch .seal = False
47+ return arch
48+
1549
1650class CloudFS (BaseCommand ):
1751 """
@@ -149,29 +183,25 @@ def __init__(self, core):
149183 self .locks = Locks (self ._core )
150184
151185 def _get_entire_object (self , name , owner ):
152- return self ._core .api .get (f'{ self .find (name , owner , include = ["baseObjectRef" ]).baseObjectRef } ' )
186+ # Fusion Direct fields (``openFabricSettings`` / ``openStorageEnabled``) for read-modify-write on PUT.
187+ include = union (CLOUD_DRIVE_FUSION_DIRECT_INCLUDE , ['baseObjectRef' ])
188+ ref = self .find (name , owner , include = include ).baseObjectRef
189+ return self ._core .api .get (ref )
153190
154- def add (self , name , group , owner , winacls = True , description = None , # pylint: disable=too-many-arguments
155- quota = None , compliance_settings = None , xattrs = None , gfl = False , lock_extensions = None ):
156- """
157- Create a new Cloud Drive Folder (Cloud Volume)
191+ @staticmethod
192+ def _parse_add_cloud_drive_response (response ):
193+ if isinstance (response , str ):
194+ match = re .search (r'/Users/(.+)' , response )
195+ if match :
196+ return match .group ()
197+ return response
198+ return response
158199
159- :param str name: Name of the new folder
160- :param str group: Folder Group to assign this folder to
161- :param cterasdk.core.types.UserAccount owner: User account, the owner of the new folder
162- :param bool,optional winacls: Use Windows ACLs, defaults to True
163- :param str,optional description: Cloud drive folder description
164- :param str,optional quota: Cloud drive folder quota in GB
165- :param cterasdk.common.object.Object,optional compliance_settings: Compliance settings, defaults to disabled.
166- Use :func:`cterasdk.core.types.ComplianceSettingsBuilder` to build the compliance settings object
167- :param cterasdk.common.object.Object,optional xattrs: Extended attributes, defaults to MacOS.
168- Use :func:`cterasdk.core.types.ExtendedAttributesBuilder` to build the extended attributes object
169- :param bool,optional gfl: Enable global file locking
170- :param list[str],optional lock_extensions: List of file extensions (without leading dot) for which global file locking is enforced.
171- :returns: Path to the Cloud Drive folder
172- :rtype: str
173- """
200+ def _build_cloud_drive_create_params (self , name , group , owner , winacls = True , description = None , # pylint: disable=too-many-arguments
201+ quota = None , compliance_settings = None , xattrs = None , gfl = False , lock_extensions = None ,
202+ archive_settings = None ):
174203 param = Object ()
204+ param ._classname = 'CloudDriveCreateParams' # pylint: disable=protected-access
175205 param .name = name
176206 param .owner = self ._core .users .get (owner , ['baseObjectRef' ]).baseObjectRef
177207 param .group = self ._core .cloudfs .groups .get (group , ['baseObjectRef' ]).baseObjectRef
@@ -193,21 +223,124 @@ def add(self, name, group, owner, winacls=True, description=None, # pylint: dis
193223 else :
194224 param .extendedAttributes = ExtendedAttributesBuilder .default ().build ()
195225
226+ param .archiveSettings = archive_settings if archive_settings is not None else _default_archive_settings ()
196227 if gfl :
197228 param .globalFileLockSettings = Object ()
198229 param .globalFileLockSettings ._classname = 'GlobalFileLockSettings' # pylint: disable=protected-access
199230 param .globalFileLockSettings .enabled = True
200231 param .globalFileLockSettings .globalFileLockExtensions = (
201232 lock_extensions if lock_extensions else CloudDrives .default_extensions
202233 )
234+ else :
235+ param .globalFileLockSettings = Object ()
236+ param .globalFileLockSettings ._classname = 'GlobalFileLockSettings' # pylint: disable=protected-access
237+ param .globalFileLockSettings .enabled = False
238+ param .globalFileLockSettings .globalFileLockExtensions = (
239+ lock_extensions if lock_extensions else CloudDrives .default_extensions
240+ )
241+
242+ return param
243+
244+ @staticmethod
245+ def _apply_fusion_direct_storage_params (param , open_fabric_settings , open_storage_enabled ):
246+ if open_fabric_settings is not None :
247+ if open_storage_enabled is False :
248+ raise CTERAException (
249+ 'openStorageEnabled cannot be False when openFabricSettings is set.'
250+ )
251+ param .openFabricSettings = open_fabric_settings
252+ param .openStorageEnabled = True if open_storage_enabled is None else bool (open_storage_enabled )
253+ elif open_storage_enabled is not None :
254+ param .openStorageEnabled = open_storage_enabled
255+
256+ @staticmethod
257+ def _apply_fusion_direct_storage_modify_params (param , open_fabric_settings , open_storage_enabled ):
258+ if open_fabric_settings is not None :
259+ if open_storage_enabled is False :
260+ raise CTERAException (
261+ 'openStorageEnabled cannot be False when openFabricSettings is set.'
262+ )
263+ param .openFabricSettings = open_fabric_settings
264+ if open_storage_enabled is not None :
265+ param .openStorageEnabled = bool (open_storage_enabled )
266+ elif open_storage_enabled is not None :
267+ param .openStorageEnabled = open_storage_enabled
268+
269+ def add (self , name , group , owner , winacls = True , description = None , # pylint: disable=too-many-arguments, too-many-locals
270+ quota = None , compliance_settings = None , xattrs = None , gfl = False , lock_extensions = None ,
271+ open_fabric_settings = None , open_storage_enabled = None , archive_settings = None ):
272+ """
273+ Create a new Cloud Drive Folder (Cloud Volume)
274+
275+ :param str name: Name of the new folder
276+ :param str group: Folder Group to assign this folder to
277+ :param cterasdk.core.types.UserAccount owner: User account, the owner of the new folder
278+ :param bool,optional winacls: Use Windows ACLs, defaults to True
279+ :param str,optional description: Cloud drive folder description
280+ :param str,optional quota: Cloud drive folder quota in GB
281+ :param cterasdk.common.object.Object,optional compliance_settings: Compliance settings, defaults to disabled.
282+ Use :func:`cterasdk.core.types.ComplianceSettingsBuilder` to build the compliance settings object
283+ :param cterasdk.common.object.Object,optional xattrs: Extended attributes, defaults to MacOS.
284+ Use :func:`cterasdk.core.types.ExtendedAttributesBuilder` to build the extended attributes object
285+ :param bool,optional gfl: Enable global file locking
286+ :param list[str],optional lock_extensions: List of file extensions (without leading dot) for which global file locking is enforced.
287+ :param cterasdk.common.object.Object,optional open_fabric_settings: Fusion Direct payload: ``OpenFabricSettings`` object tree (e.g. from
288+ :class:`cterasdk.core.fusion_direct.OpenFabricSettingsBuilder`). When set, ``openStorageEnabled`` defaults to ``True``;
289+ passing ``open_storage_enabled=False`` raises :class:`cterasdk.exceptions.CTERAException`.
290+ :param bool,optional open_storage_enabled: When ``open_fabric_settings`` is omitted, sets ``openStorageEnabled`` on the
291+ create payload if not ``None``. Must not be ``False`` when ``open_fabric_settings`` is set.
292+ :param cterasdk.common.object.Object,optional archive_settings: ``ArchiveSettings`` object tree. When omitted, a portal-aligned
293+ default (archive off, enterprise retention) built by ``_default_archive_settings()`` is used.
294+ :returns: WebDAV-style ``/Users/...`` path string when the portal returns that form; otherwise the raw execute response.
295+ :rtype: str or object
296+ """
297+ param = self ._build_cloud_drive_create_params (
298+ name , group , owner , winacls , description , quota , compliance_settings , xattrs , gfl , lock_extensions ,
299+ archive_settings = archive_settings )
300+ self ._apply_fusion_direct_storage_params (param , open_fabric_settings , open_storage_enabled )
203301
204302 try :
205303 response = self ._core .api .execute ('' , 'addCloudDrive' , param )
206304 logger .info (
207305 'Cloud drive folder created. %s' ,
208306 {'name' : name , 'owner' : param .owner , 'folder_group' : group , 'winacls' : winacls }
209307 )
210- return re .search (r'/Users\/(.+)' , response ).group ()
308+ return CloudDrives ._parse_add_cloud_drive_response (response )
309+ except CTERAException as error :
310+ logger .error (
311+ 'Cloud drive folder creation failed. %s' ,
312+ {'name' : name , 'folder_group' : group , 'owner' : str (owner ), 'win_acls' : winacls }
313+ )
314+ raise error
315+
316+ def add_return_id (self , name , group , owner , winacls = True , description = None , # pylint: disable=too-many-arguments, too-many-locals
317+ quota = None , compliance_settings = None , xattrs = None , gfl = False , lock_extensions = None ,
318+ open_fabric_settings = None , open_storage_enabled = None , archive_settings = None ):
319+ """
320+ Create a Cloud Drive Folder using ``addCloudDriveReturnID`` (structured return codes and folder id).
321+
322+ Parameters match :meth:`add`. Inspect ``rc`` and ``folderEntry`` on the returned object for success or errors.
323+
324+ .. note::
325+ On some portal builds, a **global admin** session creating a folder for **another** user can leave the
326+ folder created in the database yet return HTTP 500, because the ReturnID response path resolves the new
327+ folder under the caller's principal instead of the owner. Prefer :meth:`add` in that scenario, or verify
328+ the folder in the UI / listing before retrying with the same name.
329+
330+ :returns: Parsed portal response (typically XML-backed object with ``rc``, ``folderEntry``, ``errorMsg``, etc.)
331+ """
332+ param = self ._build_cloud_drive_create_params (
333+ name , group , owner , winacls , description , quota , compliance_settings , xattrs , gfl , lock_extensions ,
334+ archive_settings = archive_settings )
335+ self ._apply_fusion_direct_storage_params (param , open_fabric_settings , open_storage_enabled )
336+
337+ try :
338+ response = self ._core .api .execute ('' , 'addCloudDriveReturnID' , param )
339+ logger .info (
340+ 'Cloud drive folder create (return id). %s' ,
341+ {'name' : name , 'owner' : param .owner , 'folder_group' : group , 'winacls' : winacls }
342+ )
343+ return response
211344 except CTERAException as error :
212345 logger .error (
213346 'Cloud drive folder creation failed. %s' ,
@@ -217,10 +350,13 @@ def add(self, name, group, owner, winacls=True, description=None, # pylint: dis
217350
218351 def modify (self , current_name , owner , new_name = None , # pylint: disable=too-many-arguments, too-many-locals
219352 new_owner = None , new_group = None , description = None , winacls = None , quota = None , compliance_settings = None , xattrs = None ,
220- gfl = None , lock_extensions = None ):
353+ gfl = None , lock_extensions = None , open_fabric_settings = None , open_storage_enabled = None ):
221354 """
222355 Modify a Cloud Drive Folder (Cloud Volume)
223356
357+ The folder is loaded with :data:`CLOUD_DRIVE_FUSION_DIRECT_INCLUDE` so
358+ ``openFabricSettings`` / ``openStorageEnabled`` survive fields you do not change.
359+
224360 :param str current_name: Current folder name
225361 :param cterasdk.core.types.UserAccount owner: User account, the owner of the folder
226362 :param str,optional new_name: New folder name
@@ -235,6 +371,13 @@ def modify(self, current_name, owner, new_name=None, # pylint: disable=too-many
235371 Use :func:`cterasdk.core.types.ExtendedAttributesBuilder` to build the extended attributes object
236372 :param bool,optional gfl: Enable global file locking
237373 :param list[str],optional lock_extensions: List of file extensions (without leading dot) for which global file locking is enforced.
374+ :param cterasdk.common.object.Object,optional open_fabric_settings: Replacement Fusion Direct ``OpenFabricSettings`` object tree
375+ (e.g. from :class:`cterasdk.core.fusion_direct.OpenFabricSettingsBuilder`). The portal forbids changing S3 bucket or
376+ storage driver type after creation; ``storageMode`` and credentials may be updated within those rules.
377+ To keep the existing secret when rebuilding ``dataStorage``, set ``secretKey`` to
378+ :data:`cterasdk.core.fusion_direct.FUSION_DIRECT_SECRET_KEY_UNCHANGED`.
379+ :param bool,optional open_storage_enabled: When ``open_fabric_settings`` is omitted, sets ``openStorageEnabled`` if not ``None``.
380+ The portal rejects disabling Fusion Direct after creation; must not be ``False`` when ``open_fabric_settings`` is set.
238381 """
239382 param = self ._get_entire_object (current_name , owner )
240383 if new_name :
@@ -260,6 +403,8 @@ def modify(self, current_name, owner, new_name=None, # pylint: disable=too-many
260403 lock_extensions if lock_extensions else CloudDrives .default_extensions
261404 )
262405
406+ self ._apply_fusion_direct_storage_modify_params (param , open_fabric_settings , open_storage_enabled )
407+
263408 try :
264409 response = self ._core .api .put (f'/{ param .baseObjectRef } ' , param )
265410 logger .info ('Cloud drive folder updated. %s' , {'name' : current_name })
0 commit comments