1# -------------------------------------------------------------------------
2# Copyright (c) Microsoft Corporation. All rights reserved.
3# Licensed under the MIT License. See License.txt in the project root for
4# license information.
5# --------------------------------------------------------------------------
6
7import functools
8from typing import (  # pylint: disable=unused-import
9    Union, Optional, Any, Dict, List,
10    TYPE_CHECKING
11)
12try:
13    from urllib.parse import urlparse
14except ImportError:
15    from urlparse import urlparse # type: ignore
16
17from azure.core.paging import ItemPaged
18from azure.core.tracing.decorator import distributed_trace
19from azure.core.pipeline import Pipeline
20from ._shared.base_client import StorageAccountHostsMixin, TransportWrapper, parse_connection_str, parse_query
21from ._shared.response_handlers import process_storage_error
22from ._generated import AzureFileStorage
23from ._generated.models import StorageErrorException, StorageServiceProperties
24from ._generated.version import VERSION
25from ._share_client import ShareClient
26from ._serialize import get_api_version
27from ._models import (
28    SharePropertiesPaged,
29    service_properties_deserialize,
30)
31
32if TYPE_CHECKING:
33    from datetime import datetime
34    from ._models import (
35        ShareProperties,
36        Metrics,
37        CorsRule,
38    )
39
40
41class ShareServiceClient(StorageAccountHostsMixin):
42    """A client to interact with the File Share Service at the account level.
43
44    This client provides operations to retrieve and configure the account properties
45    as well as list, create and delete shares within the account.
46    For operations relating to a specific share, a client for that entity
47    can also be retrieved using the :func:`get_share_client` function.
48
49    :param str account_url:
50        The URL to the file share storage account. Any other entities included
51        in the URL path (e.g. share or file) will be discarded. This URL can be optionally
52        authenticated with a SAS token.
53    :param credential:
54        The credential with which to authenticate. This is optional if the
55        account URL already has a SAS token. The value can be a SAS token string or an account
56        shared access key.
57    :keyword str api_version:
58        The Storage API version to use for requests. Default value is '2019-07-07'.
59        Setting to an older version may result in reduced feature compatibility.
60
61        .. versionadded:: 12.1.0
62
63    :keyword str secondary_hostname:
64        The hostname of the secondary endpoint.
65    :keyword int max_range_size: The maximum range size used for a file upload. Defaults to 4*1024*1024.
66
67    .. admonition:: Example:
68
69        .. literalinclude:: ../samples/file_samples_authentication.py
70            :start-after: [START create_share_service_client]
71            :end-before: [END create_share_service_client]
72            :language: python
73            :dedent: 8
74            :caption: Create the share service client with url and credential.
75    """
76    def __init__(
77            self, account_url,  # type: str
78            credential=None,  # type: Optional[Any]
79            **kwargs  # type: Any
80        ):
81        # type: (...) -> None
82        try:
83            if not account_url.lower().startswith('http'):
84                account_url = "https://" + account_url
85        except AttributeError:
86            raise ValueError("Account URL must be a string.")
87        parsed_url = urlparse(account_url.rstrip('/'))
88        if not parsed_url.netloc:
89            raise ValueError("Invalid URL: {}".format(account_url))
90        if hasattr(credential, 'get_token'):
91            raise ValueError("Token credentials not supported by the File Share service.")
92
93        _, sas_token = parse_query(parsed_url.query)
94        if not sas_token and not credential:
95            raise ValueError(
96                'You need to provide either an account shared key or SAS token when creating a storage service.')
97        self._query_str, credential = self._format_query_string(sas_token, credential)
98        super(ShareServiceClient, self).__init__(parsed_url, service='file-share', credential=credential, **kwargs)
99        self._client = AzureFileStorage(version=VERSION, url=self.url, pipeline=self._pipeline)
100        self._client._config.version = get_api_version(kwargs, VERSION)  # pylint: disable=protected-access
101
102    def _format_url(self, hostname):
103        """Format the endpoint URL according to the current location
104        mode hostname.
105        """
106        return "{}://{}/{}".format(self.scheme, hostname, self._query_str)
107
108    @classmethod
109    def from_connection_string(
110            cls, conn_str,  # type: str
111            credential=None, # type: Optional[Any]
112            **kwargs  # type: Any
113        ):  # type: (...) -> ShareServiceClient
114        """Create ShareServiceClient from a Connection String.
115
116        :param str conn_str:
117            A connection string to an Azure Storage account.
118        :param credential:
119            The credential with which to authenticate. This is optional if the
120            account URL already has a SAS token. The value can be a SAS token string or an account
121            shared access key.
122        :returns: A File Share service client.
123        :rtype: ~azure.storage.fileshare.ShareServiceClient
124
125        .. admonition:: Example:
126
127            .. literalinclude:: ../samples/file_samples_authentication.py
128                :start-after: [START create_share_service_client_from_conn_string]
129                :end-before: [END create_share_service_client_from_conn_string]
130                :language: python
131                :dedent: 8
132                :caption: Create the share service client with connection string.
133        """
134        account_url, secondary, credential = parse_connection_str(conn_str, credential, 'file')
135        if 'secondary_hostname' not in kwargs:
136            kwargs['secondary_hostname'] = secondary
137        return cls(account_url, credential=credential, **kwargs)
138
139    @distributed_trace
140    def get_service_properties(self, **kwargs):
141        # type: (Any) -> Dict[str, Any]
142        """Gets the properties of a storage account's File Share service, including
143        Azure Storage Analytics.
144
145        :keyword int timeout:
146            The timeout parameter is expressed in seconds.
147        :returns: A dictionary containing file service properties such as
148            analytics logging, hour/minute metrics, cors rules, etc.
149        :rtype: Dict[str, Any]
150
151        .. admonition:: Example:
152
153            .. literalinclude:: ../samples/file_samples_service.py
154                :start-after: [START get_service_properties]
155                :end-before: [END get_service_properties]
156                :language: python
157                :dedent: 8
158                :caption: Get file share service properties.
159        """
160        timeout = kwargs.pop('timeout', None)
161        try:
162            service_props = self._client.service.get_properties(timeout=timeout, **kwargs)
163            return service_properties_deserialize(service_props)
164        except StorageErrorException as error:
165            process_storage_error(error)
166
167    @distributed_trace
168    def set_service_properties(
169            self, hour_metrics=None,  # type: Optional[Metrics]
170            minute_metrics=None,  # type: Optional[Metrics]
171            cors=None,  # type: Optional[List[CorsRule]]
172            **kwargs
173        ):
174        # type: (...) -> None
175        """Sets the properties of a storage account's File Share service, including
176        Azure Storage Analytics. If an element (e.g. hour_metrics) is left as None, the
177        existing settings on the service for that functionality are preserved.
178
179        :param hour_metrics:
180            The hour metrics settings provide a summary of request
181            statistics grouped by API in hourly aggregates for files.
182        :type hour_metrics: ~azure.storage.fileshare.Metrics
183        :param minute_metrics:
184            The minute metrics settings provide request statistics
185            for each minute for files.
186        :type minute_metrics: ~azure.storage.fileshare.Metrics
187        :param cors:
188            You can include up to five CorsRule elements in the
189            list. If an empty list is specified, all CORS rules will be deleted,
190            and CORS will be disabled for the service.
191        :type cors: list(:class:`~azure.storage.fileshare.CorsRule`)
192        :keyword int timeout:
193            The timeout parameter is expressed in seconds.
194        :rtype: None
195
196        .. admonition:: Example:
197
198            .. literalinclude:: ../samples/file_samples_service.py
199                :start-after: [START set_service_properties]
200                :end-before: [END set_service_properties]
201                :language: python
202                :dedent: 8
203                :caption: Sets file share service properties.
204        """
205        timeout = kwargs.pop('timeout', None)
206        props = StorageServiceProperties(
207            hour_metrics=hour_metrics,
208            minute_metrics=minute_metrics,
209            cors=cors
210        )
211        try:
212            self._client.service.set_properties(props, timeout=timeout, **kwargs)
213        except StorageErrorException as error:
214            process_storage_error(error)
215
216    @distributed_trace
217    def list_shares(
218            self, name_starts_with=None,  # type: Optional[str]
219            include_metadata=False,  # type: Optional[bool]
220            include_snapshots=False, # type: Optional[bool]
221            **kwargs
222        ):
223        # type: (...) -> ItemPaged[ShareProperties]
224        """Returns auto-paging iterable of dict-like ShareProperties under the specified account.
225        The generator will lazily follow the continuation tokens returned by
226        the service and stop when all shares have been returned.
227
228        :param str name_starts_with:
229            Filters the results to return only shares whose names
230            begin with the specified name_starts_with.
231        :param bool include_metadata:
232            Specifies that share metadata be returned in the response.
233        :param bool include_snapshots:
234            Specifies that share snapshot be returned in the response.
235        :keyword bool include_deleted:
236            Specifies that deleted shares be returned in the response.
237            This is only for share soft delete enabled account.
238        :keyword int timeout:
239            The timeout parameter is expressed in seconds.
240        :returns: An iterable (auto-paging) of ShareProperties.
241        :rtype: ~azure.core.paging.ItemPaged[~azure.storage.fileshare.ShareProperties]
242
243        .. admonition:: Example:
244
245            .. literalinclude:: ../samples/file_samples_service.py
246                :start-after: [START fsc_list_shares]
247                :end-before: [END fsc_list_shares]
248                :language: python
249                :dedent: 12
250                :caption: List shares in the file share service.
251        """
252        timeout = kwargs.pop('timeout', None)
253        include = []
254        include_deleted = kwargs.pop('include_deleted', None)
255        if include_deleted:
256            include.append("deleted")
257        if include_metadata:
258            include.append('metadata')
259        if include_snapshots:
260            include.append('snapshots')
261
262        results_per_page = kwargs.pop('results_per_page', None)
263        command = functools.partial(
264            self._client.service.list_shares_segment,
265            include=include,
266            timeout=timeout,
267            **kwargs)
268        return ItemPaged(
269            command, prefix=name_starts_with, results_per_page=results_per_page,
270            page_iterator_class=SharePropertiesPaged)
271
272    @distributed_trace
273    def create_share(
274            self, share_name,  # type: str
275            **kwargs
276        ):
277        # type: (...) -> ShareClient
278        """Creates a new share under the specified account. If the share
279        with the same name already exists, the operation fails. Returns a client with
280        which to interact with the newly created share.
281
282        :param str share_name: The name of the share to create.
283        :keyword dict(str,str) metadata:
284            A dict with name_value pairs to associate with the
285            share as metadata. Example:{'Category':'test'}
286        :keyword int quota:
287            Quota in bytes.
288        :keyword int timeout:
289            The timeout parameter is expressed in seconds.
290        :rtype: ~azure.storage.fileshare.ShareClient
291
292        .. admonition:: Example:
293
294            .. literalinclude:: ../samples/file_samples_service.py
295                :start-after: [START fsc_create_shares]
296                :end-before: [END fsc_create_shares]
297                :language: python
298                :dedent: 8
299                :caption: Create a share in the file share service.
300        """
301        metadata = kwargs.pop('metadata', None)
302        quota = kwargs.pop('quota', None)
303        timeout = kwargs.pop('timeout', None)
304        share = self.get_share_client(share_name)
305        kwargs.setdefault('merge_span', True)
306        share.create_share(metadata=metadata, quota=quota, timeout=timeout, **kwargs)
307        return share
308
309    @distributed_trace
310    def delete_share(
311            self, share_name,  # type: Union[ShareProperties, str]
312            delete_snapshots=False, # type: Optional[bool]
313            **kwargs
314        ):
315        # type: (...) -> None
316        """Marks the specified share for deletion. The share is
317        later deleted during garbage collection.
318
319        :param share_name:
320            The share to delete. This can either be the name of the share,
321            or an instance of ShareProperties.
322        :type share_name: str or ~azure.storage.fileshare.ShareProperties
323        :param bool delete_snapshots:
324            Indicates if snapshots are to be deleted.
325        :keyword int timeout:
326            The timeout parameter is expressed in seconds.
327        :rtype: None
328
329        .. admonition:: Example:
330
331            .. literalinclude:: ../samples/file_samples_service.py
332                :start-after: [START fsc_delete_shares]
333                :end-before: [END fsc_delete_shares]
334                :language: python
335                :dedent: 12
336                :caption: Delete a share in the file share service.
337        """
338        timeout = kwargs.pop('timeout', None)
339        share = self.get_share_client(share_name)
340        kwargs.setdefault('merge_span', True)
341        share.delete_share(
342            delete_snapshots=delete_snapshots, timeout=timeout, **kwargs)
343
344    @distributed_trace
345    def undelete_share(self, deleted_share_name, deleted_share_version, **kwargs):
346        # type: (str, str, **Any) -> ShareClient
347        """Restores soft-deleted share.
348
349        Operation will only be successful if used within the specified number of days
350        set in the delete retention policy.
351
352        .. versionadded:: 12.2.0
353            This operation was introduced in API version '2019-12-12'.
354
355        :param str deleted_share_name:
356            Specifies the name of the deleted share to restore.
357        :param str deleted_share_version:
358            Specifies the version of the deleted share to restore.
359        :keyword int timeout:
360            The timeout parameter is expressed in seconds.
361        :rtype: ~azure.storage.fileshare.ShareClient
362        """
363        share = self.get_share_client(deleted_share_name)
364
365        try:
366            share._client.share.restore(deleted_share_name=deleted_share_name,  # pylint: disable = protected-access
367                                        deleted_share_version=deleted_share_version,
368                                        timeout=kwargs.pop('timeout', None), **kwargs)
369            return share
370        except StorageErrorException as error:
371            process_storage_error(error)
372
373    def get_share_client(self, share, snapshot=None):
374        # type: (Union[ShareProperties, str],Optional[Union[Dict[str, Any], str]]) -> ShareClient
375        """Get a client to interact with the specified share.
376        The share need not already exist.
377
378        :param share:
379            The share. This can either be the name of the share,
380            or an instance of ShareProperties.
381        :type share: str or ~azure.storage.fileshare.ShareProperties
382        :param str snapshot:
383            An optional share snapshot on which to operate. This can be the snapshot ID string
384            or the response returned from :func:`create_snapshot`.
385        :returns: A ShareClient.
386        :rtype: ~azure.storage.fileshare.ShareClient
387
388        .. admonition:: Example:
389
390            .. literalinclude:: ../samples/file_samples_service.py
391                :start-after: [START get_share_client]
392                :end-before: [END get_share_client]
393                :language: python
394                :dedent: 8
395                :caption: Gets the share client.
396        """
397        try:
398            share_name = share.name
399        except AttributeError:
400            share_name = share
401
402        _pipeline = Pipeline(
403            transport=TransportWrapper(self._pipeline._transport), # pylint: disable = protected-access
404            policies=self._pipeline._impl_policies # pylint: disable = protected-access
405        )
406        return ShareClient(
407            self.url, share_name=share_name, snapshot=snapshot, credential=self.credential,
408            api_version=self.api_version, _hosts=self._hosts,
409            _configuration=self._config, _pipeline=_pipeline, _location_mode=self._location_mode)
410