diff --git a/sdk/storage/azure-storage-blob/CHANGELOG.md b/sdk/storage/azure-storage-blob/CHANGELOG.md index b1647163ed78..6933752dfc4b 100644 --- a/sdk/storage/azure-storage-blob/CHANGELOG.md +++ b/sdk/storage/azure-storage-blob/CHANGELOG.md @@ -8,6 +8,8 @@ ### Bugs Fixed: - Fixed a bug in `BlobClient.from_blob_url()` such that users will receive a more helpful error message if they pass an incorrect URL without a full `/container/blob` path. +- Fixed a bug, introduced in the previous beta release, that caused Authentication errors when attempting to use +an Account SAS with certain service level operations. ## 12.12.0b1 (2022-04-14) diff --git a/sdk/storage/azure-storage-blob/azure/storage/blob/_generated/aio/operations/_service_operations.py b/sdk/storage/azure-storage-blob/azure/storage/blob/_generated/aio/operations/_service_operations.py index a789673cee5f..320d6c6d47f2 100644 --- a/sdk/storage/azure-storage-blob/azure/storage/blob/_generated/aio/operations/_service_operations.py +++ b/sdk/storage/azure-storage-blob/azure/storage/blob/_generated/aio/operations/_service_operations.py @@ -86,6 +86,7 @@ async def set_properties( # pylint: disable=inconsistent-return-statements _content = self._serialize.body(storage_service_properties, 'StorageServiceProperties', is_xml=True) request = build_set_properties_request( + url=self._config.url, restype=restype, comp=comp, version=self._config.version, @@ -119,7 +120,7 @@ async def set_properties( # pylint: disable=inconsistent-return-statements if cls: return cls(pipeline_response, None, response_headers) - set_properties.metadata = {'url': "/"} # type: ignore + set_properties.metadata = {'url': "{url}"} # type: ignore @distributed_trace_async @@ -163,6 +164,7 @@ async def get_properties( request = build_get_properties_request( + url=self._config.url, restype=restype, comp=comp, version=self._config.version, @@ -197,7 +199,7 @@ async def get_properties( return deserialized - get_properties.metadata = {'url': "/"} # type: ignore + get_properties.metadata = {'url': "{url}"} # type: ignore @distributed_trace_async @@ -242,6 +244,7 @@ async def get_statistics( request = build_get_statistics_request( + url=self._config.url, restype=restype, comp=comp, version=self._config.version, @@ -277,7 +280,7 @@ async def get_statistics( return deserialized - get_statistics.metadata = {'url': "/"} # type: ignore + get_statistics.metadata = {'url': "{url}"} # type: ignore @distributed_trace_async @@ -341,6 +344,7 @@ async def list_containers_segment( request = build_list_containers_segment_request( + url=self._config.url, comp=comp, version=self._config.version, prefix=prefix, @@ -378,7 +382,7 @@ async def list_containers_segment( return deserialized - list_containers_segment.metadata = {'url': "/"} # type: ignore + list_containers_segment.metadata = {'url': "{url}"} # type: ignore @distributed_trace_async @@ -427,6 +431,7 @@ async def get_user_delegation_key( _content = self._serialize.body(key_info, 'KeyInfo', is_xml=True) request = build_get_user_delegation_key_request( + url=self._config.url, restype=restype, comp=comp, version=self._config.version, @@ -464,7 +469,7 @@ async def get_user_delegation_key( return deserialized - get_user_delegation_key.metadata = {'url': "/"} # type: ignore + get_user_delegation_key.metadata = {'url': "{url}"} # type: ignore @distributed_trace_async @@ -496,6 +501,7 @@ async def get_account_info( # pylint: disable=inconsistent-return-statements request = build_get_account_info_request( + url=self._config.url, restype=restype, comp=comp, version=self._config.version, @@ -529,7 +535,7 @@ async def get_account_info( # pylint: disable=inconsistent-return-statements if cls: return cls(pipeline_response, None, response_headers) - get_account_info.metadata = {'url': "/"} # type: ignore + get_account_info.metadata = {'url': "{url}"} # type: ignore @distributed_trace_async @@ -579,6 +585,7 @@ async def submit_batch( _content = self._serialize.body(body, 'IO') request = build_submit_batch_request( + url=self._config.url, multipart_content_type=multipart_content_type, comp=comp, version=self._config.version, @@ -615,7 +622,7 @@ async def submit_batch( return deserialized - submit_batch.metadata = {'url': "/"} # type: ignore + submit_batch.metadata = {'url': "{url}"} # type: ignore @distributed_trace_async @@ -676,6 +683,7 @@ async def filter_blobs( request = build_filter_blobs_request( + url=self._config.url, comp=comp, version=self._config.version, timeout=timeout, @@ -713,5 +721,5 @@ async def filter_blobs( return deserialized - filter_blobs.metadata = {'url': "/"} # type: ignore + filter_blobs.metadata = {'url': "{url}"} # type: ignore diff --git a/sdk/storage/azure-storage-blob/azure/storage/blob/_generated/operations/_service_operations.py b/sdk/storage/azure-storage-blob/azure/storage/blob/_generated/operations/_service_operations.py index d0d4757d9089..c36d55633121 100644 --- a/sdk/storage/azure-storage-blob/azure/storage/blob/_generated/operations/_service_operations.py +++ b/sdk/storage/azure-storage-blob/azure/storage/blob/_generated/operations/_service_operations.py @@ -17,7 +17,7 @@ from azure.core.tracing.decorator import distributed_trace from .. import models as _models -from .._vendor import _convert_request +from .._vendor import _convert_request, _format_url_section if TYPE_CHECKING: # pylint: disable=unused-import,ungrouped-imports @@ -30,6 +30,7 @@ # fmt: off def build_set_properties_request( + url, # type: str **kwargs # type: Any ): # type: (...) -> HttpRequest @@ -42,7 +43,12 @@ def build_set_properties_request( accept = "application/xml" # Construct URL - _url = kwargs.pop("template_url", "/") + _url = kwargs.pop("template_url", "{url}") + path_format_arguments = { + "url": _SERIALIZER.url("url", url, 'str', skip_quote=True), + } + + _url = _format_url_section(_url, **path_format_arguments) # Construct parameters _query_parameters = kwargs.pop("params", {}) # type: Dict[str, Any] @@ -70,6 +76,7 @@ def build_set_properties_request( def build_get_properties_request( + url, # type: str **kwargs # type: Any ): # type: (...) -> HttpRequest @@ -81,7 +88,12 @@ def build_get_properties_request( accept = "application/xml" # Construct URL - _url = kwargs.pop("template_url", "/") + _url = kwargs.pop("template_url", "{url}") + path_format_arguments = { + "url": _SERIALIZER.url("url", url, 'str', skip_quote=True), + } + + _url = _format_url_section(_url, **path_format_arguments) # Construct parameters _query_parameters = kwargs.pop("params", {}) # type: Dict[str, Any] @@ -107,6 +119,7 @@ def build_get_properties_request( def build_get_statistics_request( + url, # type: str **kwargs # type: Any ): # type: (...) -> HttpRequest @@ -118,7 +131,12 @@ def build_get_statistics_request( accept = "application/xml" # Construct URL - _url = kwargs.pop("template_url", "/") + _url = kwargs.pop("template_url", "{url}") + path_format_arguments = { + "url": _SERIALIZER.url("url", url, 'str', skip_quote=True), + } + + _url = _format_url_section(_url, **path_format_arguments) # Construct parameters _query_parameters = kwargs.pop("params", {}) # type: Dict[str, Any] @@ -144,6 +162,7 @@ def build_get_statistics_request( def build_list_containers_segment_request( + url, # type: str **kwargs # type: Any ): # type: (...) -> HttpRequest @@ -158,7 +177,12 @@ def build_list_containers_segment_request( accept = "application/xml" # Construct URL - _url = kwargs.pop("template_url", "/") + _url = kwargs.pop("template_url", "{url}") + path_format_arguments = { + "url": _SERIALIZER.url("url", url, 'str', skip_quote=True), + } + + _url = _format_url_section(_url, **path_format_arguments) # Construct parameters _query_parameters = kwargs.pop("params", {}) # type: Dict[str, Any] @@ -191,6 +215,7 @@ def build_list_containers_segment_request( def build_get_user_delegation_key_request( + url, # type: str **kwargs # type: Any ): # type: (...) -> HttpRequest @@ -203,7 +228,12 @@ def build_get_user_delegation_key_request( accept = "application/xml" # Construct URL - _url = kwargs.pop("template_url", "/") + _url = kwargs.pop("template_url", "{url}") + path_format_arguments = { + "url": _SERIALIZER.url("url", url, 'str', skip_quote=True), + } + + _url = _format_url_section(_url, **path_format_arguments) # Construct parameters _query_parameters = kwargs.pop("params", {}) # type: Dict[str, Any] @@ -231,6 +261,7 @@ def build_get_user_delegation_key_request( def build_get_account_info_request( + url, # type: str **kwargs # type: Any ): # type: (...) -> HttpRequest @@ -240,7 +271,12 @@ def build_get_account_info_request( accept = "application/xml" # Construct URL - _url = kwargs.pop("template_url", "/") + _url = kwargs.pop("template_url", "{url}") + path_format_arguments = { + "url": _SERIALIZER.url("url", url, 'str', skip_quote=True), + } + + _url = _format_url_section(_url, **path_format_arguments) # Construct parameters _query_parameters = kwargs.pop("params", {}) # type: Dict[str, Any] @@ -262,6 +298,7 @@ def build_get_account_info_request( def build_submit_batch_request( + url, # type: str **kwargs # type: Any ): # type: (...) -> HttpRequest @@ -274,7 +311,12 @@ def build_submit_batch_request( accept = "application/xml" # Construct URL - _url = kwargs.pop("template_url", "/") + _url = kwargs.pop("template_url", "{url}") + path_format_arguments = { + "url": _SERIALIZER.url("url", url, 'str', skip_quote=True), + } + + _url = _format_url_section(_url, **path_format_arguments) # Construct parameters _query_parameters = kwargs.pop("params", {}) # type: Dict[str, Any] @@ -301,6 +343,7 @@ def build_submit_batch_request( def build_filter_blobs_request( + url, # type: str **kwargs # type: Any ): # type: (...) -> HttpRequest @@ -314,7 +357,12 @@ def build_filter_blobs_request( accept = "application/xml" # Construct URL - _url = kwargs.pop("template_url", "/") + _url = kwargs.pop("template_url", "{url}") + path_format_arguments = { + "url": _SERIALIZER.url("url", url, 'str', skip_quote=True), + } + + _url = _format_url_section(_url, **path_format_arguments) # Construct parameters _query_parameters = kwargs.pop("params", {}) # type: Dict[str, Any] @@ -411,6 +459,7 @@ def set_properties( # pylint: disable=inconsistent-return-statements _content = self._serialize.body(storage_service_properties, 'StorageServiceProperties', is_xml=True) request = build_set_properties_request( + url=self._config.url, restype=restype, comp=comp, version=self._config.version, @@ -444,7 +493,7 @@ def set_properties( # pylint: disable=inconsistent-return-statements if cls: return cls(pipeline_response, None, response_headers) - set_properties.metadata = {'url': "/"} # type: ignore + set_properties.metadata = {'url': "{url}"} # type: ignore @distributed_trace @@ -489,6 +538,7 @@ def get_properties( request = build_get_properties_request( + url=self._config.url, restype=restype, comp=comp, version=self._config.version, @@ -523,7 +573,7 @@ def get_properties( return deserialized - get_properties.metadata = {'url': "/"} # type: ignore + get_properties.metadata = {'url': "{url}"} # type: ignore @distributed_trace @@ -569,6 +619,7 @@ def get_statistics( request = build_get_statistics_request( + url=self._config.url, restype=restype, comp=comp, version=self._config.version, @@ -604,7 +655,7 @@ def get_statistics( return deserialized - get_statistics.metadata = {'url': "/"} # type: ignore + get_statistics.metadata = {'url': "{url}"} # type: ignore @distributed_trace @@ -669,6 +720,7 @@ def list_containers_segment( request = build_list_containers_segment_request( + url=self._config.url, comp=comp, version=self._config.version, prefix=prefix, @@ -706,7 +758,7 @@ def list_containers_segment( return deserialized - list_containers_segment.metadata = {'url': "/"} # type: ignore + list_containers_segment.metadata = {'url': "{url}"} # type: ignore @distributed_trace @@ -756,6 +808,7 @@ def get_user_delegation_key( _content = self._serialize.body(key_info, 'KeyInfo', is_xml=True) request = build_get_user_delegation_key_request( + url=self._config.url, restype=restype, comp=comp, version=self._config.version, @@ -793,7 +846,7 @@ def get_user_delegation_key( return deserialized - get_user_delegation_key.metadata = {'url': "/"} # type: ignore + get_user_delegation_key.metadata = {'url': "{url}"} # type: ignore @distributed_trace @@ -826,6 +879,7 @@ def get_account_info( # pylint: disable=inconsistent-return-statements request = build_get_account_info_request( + url=self._config.url, restype=restype, comp=comp, version=self._config.version, @@ -859,7 +913,7 @@ def get_account_info( # pylint: disable=inconsistent-return-statements if cls: return cls(pipeline_response, None, response_headers) - get_account_info.metadata = {'url': "/"} # type: ignore + get_account_info.metadata = {'url': "{url}"} # type: ignore @distributed_trace @@ -910,6 +964,7 @@ def submit_batch( _content = self._serialize.body(body, 'IO') request = build_submit_batch_request( + url=self._config.url, multipart_content_type=multipart_content_type, comp=comp, version=self._config.version, @@ -946,7 +1001,7 @@ def submit_batch( return deserialized - submit_batch.metadata = {'url': "/"} # type: ignore + submit_batch.metadata = {'url': "{url}"} # type: ignore @distributed_trace @@ -1008,6 +1063,7 @@ def filter_blobs( request = build_filter_blobs_request( + url=self._config.url, comp=comp, version=self._config.version, timeout=timeout, @@ -1045,5 +1101,5 @@ def filter_blobs( return deserialized - filter_blobs.metadata = {'url': "/"} # type: ignore + filter_blobs.metadata = {'url': "{url}"} # type: ignore diff --git a/sdk/storage/azure-storage-blob/swagger/README.md b/sdk/storage/azure-storage-blob/swagger/README.md index 709193dba6c8..f7f3e449548f 100644 --- a/sdk/storage/azure-storage-blob/swagger/README.md +++ b/sdk/storage/azure-storage-blob/swagger/README.md @@ -150,7 +150,7 @@ directive: $["x-ms-parameterized-host"] = undefined; ``` -### Add url parameter to each operation and add it to the url +### Add url parameter to each operation and add url to the path ``` yaml directive: - from: swagger-document @@ -158,14 +158,21 @@ directive: transform: > for (const property in $) { - // Don't apply to service operations (where path is just '/') - if (property !== '/' && !property.startsWith('/?')) { - $[property]["parameters"].push({"$ref": "#/parameters/Url"}); - - var oldName = property; + $[property]["parameters"].push({"$ref": "#/parameters/Url"}); + + var oldName = property; + // For service operations (where the path is just '/') we need to + // remove the '/' at the begining to avoid having an extra '/' in + // the final URL. + if (property === '/' || property.startsWith('/?')) + { + var newName = '{url}' + property.substring(1); + } + else + { var newName = '{url}' + property; - $[newName] = $[oldName]; - delete $[oldName]; } + $[newName] = $[oldName]; + delete $[oldName]; } ``` diff --git a/sdk/storage/azure-storage-blob/tests/test_blob_service_properties.py b/sdk/storage/azure-storage-blob/tests/test_blob_service_properties.py index a322f5cbce57..06e5ff990f13 100644 --- a/sdk/storage/azure-storage-blob/tests/test_blob_service_properties.py +++ b/sdk/storage/azure-storage-blob/tests/test_blob_service_properties.py @@ -5,20 +5,20 @@ # Licensed under the MIT License. See License.txt in the project root for # license information. # -------------------------------------------------------------------------- -import unittest +import pytest +from datetime import datetime, timedelta -from msrest.exceptions import ValidationError # TODO This should be an azure-core error. from azure.core.exceptions import HttpResponseError -from devtools_testutils import ResourceGroupPreparer, StorageAccountPreparer from azure.storage.blob import ( - BlobServiceClient, - ContainerClient, - BlobClient, + AccountSasPermissions, BlobAnalyticsLogging, - Metrics, + BlobServiceClient, CorsRule, + Metrics, + ResourceTypes, RetentionPolicy, StaticWebsite, + generate_account_sas, ) from settings.testcase import BlobPreparer @@ -383,6 +383,25 @@ def test_set_cors(self, storage_account_name, storage_account_key): received_props = bsc.get_service_properties() self._assert_cors_equal(received_props['cors'], cors) + @pytest.mark.live_test_only + @BlobPreparer() + def test_get_service_properties_account_sas(self, storage_account_name, storage_account_key): + # Arrange + sas_token = generate_account_sas( + account_name=storage_account_name, + account_key=storage_account_key, + resource_types=ResourceTypes(service=True), + permission=AccountSasPermissions(read=True), + expiry=datetime.utcnow() + timedelta(hours=3) + ) + bsc = BlobServiceClient(self.account_url(storage_account_name, "blob"), credential=sas_token) + + # Act + props = bsc.get_service_properties() + + # Assert + self.assertIsNotNone(props) + # --Test cases for errors --------------------------------------- @BlobPreparer() def test_retention_no_days(self, storage_account_name, storage_account_key): diff --git a/sdk/storage/azure-storage-blob/tests/test_blob_service_properties_async.py b/sdk/storage/azure-storage-blob/tests/test_blob_service_properties_async.py index fe75d38c0765..452d7da35a4e 100644 --- a/sdk/storage/azure-storage-blob/tests/test_blob_service_properties_async.py +++ b/sdk/storage/azure-storage-blob/tests/test_blob_service_properties_async.py @@ -5,28 +5,25 @@ # Licensed under the MIT License. See License.txt in the project root for # license information. # -------------------------------------------------------------------------- -import unittest import pytest -import asyncio +from datetime import datetime, timedelta -from msrest.exceptions import ValidationError # TODO This should be an azure-core error. from azure.core.exceptions import HttpResponseError -from devtools_testutils import ResourceGroupPreparer, StorageAccountPreparer -from azure.storage.blob.aio import ( - BlobServiceClient, - ContainerClient, - BlobClient, -) - from azure.core.pipeline.transport import AioHttpTransport from multidict import CIMultiDict, CIMultiDictProxy +from azure.storage.blob.aio import ( + BlobServiceClient, +) from azure.storage.blob import ( + AccountSasPermissions, BlobAnalyticsLogging, - Metrics, CorsRule, + Metrics, + ResourceTypes, RetentionPolicy, StaticWebsite, + generate_account_sas, ) from settings.testcase import BlobPreparer from devtools_testutils.storage.aio import AsyncStorageTestCase @@ -415,6 +412,26 @@ async def test_set_cors(self, storage_account_name, storage_account_key): received_props = await bsc.get_service_properties() self._assert_cors_equal(received_props['cors'], cors) + @pytest.mark.live_test_only + @BlobPreparer() + @AsyncStorageTestCase.await_prepared_test + async def test_get_service_properties_account_sas(self, storage_account_name, storage_account_key): + # Arrange + sas_token = generate_account_sas( + account_name=storage_account_name, + account_key=storage_account_key, + resource_types=ResourceTypes(service=True), + permission=AccountSasPermissions(read=True), + expiry=datetime.utcnow() + timedelta(hours=3) + ) + bsc = BlobServiceClient(self.account_url(storage_account_name, "blob"), credential=sas_token) + + # Act + props = await bsc.get_service_properties() + + # Assert + self.assertIsNotNone(props) + @BlobPreparer() @AsyncStorageTestCase.await_prepared_test async def test_retention_no_days(self, storage_account_name, storage_account_key): diff --git a/sdk/storage/azure-storage-blob/tests/test_container.py b/sdk/storage/azure-storage-blob/tests/test_container.py index b8135a3ffc36..d9d6296b9970 100644 --- a/sdk/storage/azure-storage-blob/tests/test_container.py +++ b/sdk/storage/azure-storage-blob/tests/test_container.py @@ -327,6 +327,31 @@ def test_list_containers_with_num_results_and_marker(self, storage_account_name, self.assertNamedItemInContainer(containers2, container_names[2]) self.assertNamedItemInContainer(containers2, container_names[3]) + @pytest.mark.live_test_only + @BlobPreparer() + def test_list_containers_account_sas(self, storage_account_name, storage_account_key): + bsc = BlobServiceClient(self.account_url(storage_account_name, "blob"), storage_account_key) + container = self._create_container(bsc) + + sas_token = generate_account_sas( + account_name=storage_account_name, + account_key=storage_account_key, + resource_types=ResourceTypes(service=True), + permission=AccountSasPermissions(list=True), + expiry=datetime.utcnow() + timedelta(hours=3) + ) + bsc = BlobServiceClient(self.account_url(storage_account_name, "blob"), credential=sas_token) + + # Act + containers = list(bsc.list_containers(name_starts_with=container.container_name)) + + # Assert + self.assertIsNotNone(containers) + self.assertEqual(len(containers), 1) + self.assertIsNotNone(containers[0]) + self.assertEqual(containers[0].name, container.container_name) + self.assertIsNone(containers[0].metadata) + @BlobPreparer() def test_set_container_metadata(self, storage_account_name, storage_account_key): bsc = BlobServiceClient(self.account_url(storage_account_name, "blob"), storage_account_key) diff --git a/sdk/storage/azure-storage-blob/tests/test_container_async.py b/sdk/storage/azure-storage-blob/tests/test_container_async.py index 0edd46d08ab1..b7aed5b4266f 100644 --- a/sdk/storage/azure-storage-blob/tests/test_container_async.py +++ b/sdk/storage/azure-storage-blob/tests/test_container_async.py @@ -415,6 +415,34 @@ async def test_list_containers_with_num_results_and_marker(self, storage_account self.assertNamedItemInContainer(containers2, container_names[2]) self.assertNamedItemInContainer(containers2, container_names[3]) + @pytest.mark.live_test_only + @BlobPreparer() + @AsyncStorageTestCase.await_prepared_test + async def test_list_containers_account_sas(self, storage_account_name, storage_account_key): + bsc = BlobServiceClient(self.account_url(storage_account_name, "blob"), storage_account_key) + container = await self._create_container(bsc) + + sas_token = generate_account_sas( + account_name=storage_account_name, + account_key=storage_account_key, + resource_types=ResourceTypes(service=True), + permission=AccountSasPermissions(list=True), + expiry=datetime.utcnow() + timedelta(hours=3) + ) + bsc = BlobServiceClient(self.account_url(storage_account_name, "blob"), credential=sas_token) + + # Act + containers = [] + async for c in bsc.list_containers(name_starts_with=container.container_name): + containers.append(c) + + # Assert + self.assertIsNotNone(containers) + self.assertEqual(len(containers), 1) + self.assertIsNotNone(containers[0]) + self.assertEqual(containers[0].name, container.container_name) + self.assertIsNone(containers[0].metadata) + @BlobPreparer() @AsyncStorageTestCase.await_prepared_test async def test_set_container_metadata(self, storage_account_name, storage_account_key): diff --git a/sdk/storage/azure-storage-file-datalake/CHANGELOG.md b/sdk/storage/azure-storage-file-datalake/CHANGELOG.md index 8820b413335e..53d64f83ffb5 100644 --- a/sdk/storage/azure-storage-file-datalake/CHANGELOG.md +++ b/sdk/storage/azure-storage-file-datalake/CHANGELOG.md @@ -5,6 +5,8 @@ ### Features Added ### Bugs Fixed +- Fixed a bug, introduced in the previous beta release, that caused Authentication errors when attempting to use +an Account SAS with certain service level operations. ## 12.7.0b1 (2022-04-14) diff --git a/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_generated/aio/operations/_service_operations.py b/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_generated/aio/operations/_service_operations.py index c6eda977b3ba..88e25d49d741 100644 --- a/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_generated/aio/operations/_service_operations.py +++ b/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_generated/aio/operations/_service_operations.py @@ -97,6 +97,7 @@ def prepare_request(next_link=None): if not next_link: request = build_list_file_systems_request( + url=self._config.url, resource=resource, version=self._config.version, prefix=prefix, @@ -112,6 +113,7 @@ def prepare_request(next_link=None): else: request = build_list_file_systems_request( + url=self._config.url, resource=resource, version=self._config.version, prefix=prefix, @@ -154,4 +156,4 @@ async def get_next(next_link=None): return AsyncItemPaged( get_next, extract_data ) - list_file_systems.metadata = {'url': "/"} # type: ignore + list_file_systems.metadata = {'url': "{url}"} # type: ignore diff --git a/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_generated/operations/_service_operations.py b/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_generated/operations/_service_operations.py index 47770b7444aa..1154d22edb10 100644 --- a/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_generated/operations/_service_operations.py +++ b/sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_generated/operations/_service_operations.py @@ -18,7 +18,7 @@ from azure.core.tracing.decorator import distributed_trace from .. import models as _models -from .._vendor import _convert_request +from .._vendor import _convert_request, _format_url_section if TYPE_CHECKING: # pylint: disable=unused-import,ungrouped-imports @@ -31,6 +31,7 @@ # fmt: off def build_list_file_systems_request( + url, # type: str **kwargs # type: Any ): # type: (...) -> HttpRequest @@ -44,7 +45,12 @@ def build_list_file_systems_request( accept = "application/json" # Construct URL - _url = kwargs.pop("template_url", "/") + _url = kwargs.pop("template_url", "{url}") + path_format_arguments = { + "url": _SERIALIZER.url("url", url, 'str', skip_quote=True), + } + + _url = _format_url_section(_url, **path_format_arguments) # Construct parameters _query_parameters = kwargs.pop("params", {}) # type: Dict[str, Any] @@ -150,6 +156,7 @@ def prepare_request(next_link=None): if not next_link: request = build_list_file_systems_request( + url=self._config.url, resource=resource, version=self._config.version, prefix=prefix, @@ -165,6 +172,7 @@ def prepare_request(next_link=None): else: request = build_list_file_systems_request( + url=self._config.url, resource=resource, version=self._config.version, prefix=prefix, @@ -207,4 +215,4 @@ def get_next(next_link=None): return ItemPaged( get_next, extract_data ) - list_file_systems.metadata = {'url': "/"} # type: ignore + list_file_systems.metadata = {'url': "{url}"} # type: ignore diff --git a/sdk/storage/azure-storage-file-datalake/swagger/README.md b/sdk/storage/azure-storage-file-datalake/swagger/README.md index 682fd7a727a5..c9c06613285e 100644 --- a/sdk/storage/azure-storage-file-datalake/swagger/README.md +++ b/sdk/storage/azure-storage-file-datalake/swagger/README.md @@ -75,7 +75,7 @@ directive: $["x-ms-parameterized-host"] = undefined; ``` -### Add url parameter to each operation and add it to the url +### Add url parameter to each operation and add url to the path ``` yaml directive: - from: swagger-document @@ -83,18 +83,25 @@ directive: transform: > for (const property in $) { - // Don't apply to service operations (where path is just '/') - if (property !== '/' && !property.startsWith('/?')) { - if ($[property]["parameters"] === undefined) - { - $[property]["parameters"] = [] - } - $[property]["parameters"].push({"$ref": "#/parameters/Url"}); - - var oldName = property; + if ($[property]["parameters"] === undefined) + { + $[property]["parameters"] = [] + } + $[property]["parameters"].push({"$ref": "#/parameters/Url"}); + + var oldName = property; + // For service operations (where the path is just '/') we need to + // remove the '/' at the begining to avoid having an extra '/' in + // the final URL. + if (property === '/' || property.startsWith('/?')) + { + var newName = '{url}' + property.substring(1); + } + else + { var newName = '{url}' + property; - $[newName] = $[oldName]; - delete $[oldName]; } + $[newName] = $[oldName]; + delete $[oldName]; } ``` diff --git a/sdk/storage/azure-storage-file-datalake/tests/test_file_system.py b/sdk/storage/azure-storage-file-datalake/tests/test_file_system.py index fffe5e1c35a6..63a735414dba 100644 --- a/sdk/storage/azure-storage-file-datalake/tests/test_file_system.py +++ b/sdk/storage/azure-storage-file-datalake/tests/test_file_system.py @@ -158,6 +158,31 @@ def test_list_file_systemss(self, datalake_storage_account_name, datalake_storag self.assertIsNotNone(file_systems[0].has_immutability_policy) self.assertIsNotNone(file_systems[0].has_legal_hold) + @pytest.mark.live_test_only + @DataLakePreparer() + def test_list_file_systems_account_sas(self, datalake_storage_account_name, datalake_storage_account_key): + self._setUp(datalake_storage_account_name, datalake_storage_account_key) + # Arrange + file_system_name = self._get_file_system_reference() + file_system = self.dsc.create_file_system(file_system_name) + sas_token = generate_account_sas( + datalake_storage_account_name, + datalake_storage_account_key, + ResourceTypes(service=True), + AccountSasPermissions(list=True), + datetime.utcnow() + timedelta(hours=1), + ) + + # Act + dsc = DataLakeServiceClient(self.account_url(datalake_storage_account_name, 'dfs'), credential=sas_token) + file_systems = list(dsc.list_file_systems()) + + # Assert + self.assertIsNotNone(file_systems) + self.assertGreaterEqual(len(file_systems), 1) + self.assertIsNotNone(file_systems[0]) + self.assertNamedItemInContainer(file_systems, file_system.file_system_name) + @DataLakePreparer() def test_rename_file_system(self, datalake_storage_account_name, datalake_storage_account_key): if not self.is_playback(): diff --git a/sdk/storage/azure-storage-file-datalake/tests/test_file_system_async.py b/sdk/storage/azure-storage-file-datalake/tests/test_file_system_async.py index c9faa4e7f693..3d29e6af0053 100644 --- a/sdk/storage/azure-storage-file-datalake/tests/test_file_system_async.py +++ b/sdk/storage/azure-storage-file-datalake/tests/test_file_system_async.py @@ -159,6 +159,33 @@ async def test_list_file_systems_async(self, datalake_storage_account_name, data self.assertIsNotNone(file_systems[0].has_immutability_policy) self.assertIsNotNone(file_systems[0].has_legal_hold) + @pytest.mark.live_test_only + @DataLakePreparer() + async def test_list_file_systems_account_sas(self, datalake_storage_account_name, datalake_storage_account_key): + self._setUp(datalake_storage_account_name, datalake_storage_account_key) + # Arrange + file_system_name = self._get_file_system_reference() + file_system = await self.dsc.create_file_system(file_system_name) + sas_token = generate_account_sas( + datalake_storage_account_name, + datalake_storage_account_key, + ResourceTypes(service=True), + AccountSasPermissions(list=True), + datetime.utcnow() + timedelta(hours=1), + ) + + # Act + dsc = DataLakeServiceClient(self.account_url(datalake_storage_account_name, 'dfs'), credential=sas_token) + file_systems = [] + async for filesystem in dsc.list_file_systems(): + file_systems.append(filesystem) + + # Assert + self.assertIsNotNone(file_systems) + self.assertGreaterEqual(len(file_systems), 1) + self.assertIsNotNone(file_systems[0]) + self.assertNamedItemInContainer(file_systems, file_system.file_system_name) + @DataLakePreparer() async def test_delete_file_system_with_existing_file_system_async( self, datalake_storage_account_name, datalake_storage_account_key): diff --git a/sdk/storage/azure-storage-file-share/CHANGELOG.md b/sdk/storage/azure-storage-file-share/CHANGELOG.md index e986a86b1482..779062eaa914 100644 --- a/sdk/storage/azure-storage-file-share/CHANGELOG.md +++ b/sdk/storage/azure-storage-file-share/CHANGELOG.md @@ -5,6 +5,8 @@ ### Features Added ### Bugs Fixed +- Fixed a bug, introduced in the previous beta release, that caused Authentication errors when attempting to use +an Account SAS with certain service level operations. ## 12.8.0b1 (2022-04-14) diff --git a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_generated/aio/operations/_service_operations.py b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_generated/aio/operations/_service_operations.py index d334fe3be33c..ca3eb13ab0b2 100644 --- a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_generated/aio/operations/_service_operations.py +++ b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_generated/aio/operations/_service_operations.py @@ -81,6 +81,7 @@ async def set_properties( # pylint: disable=inconsistent-return-statements _content = self._serialize.body(storage_service_properties, 'StorageServiceProperties', is_xml=True) request = build_set_properties_request( + url=self._config.url, restype=restype, comp=comp, version=self._config.version, @@ -112,7 +113,7 @@ async def set_properties( # pylint: disable=inconsistent-return-statements if cls: return cls(pipeline_response, None, response_headers) - set_properties.metadata = {'url': "/"} # type: ignore + set_properties.metadata = {'url': "{url}"} # type: ignore @distributed_trace_async @@ -151,6 +152,7 @@ async def get_properties( request = build_get_properties_request( + url=self._config.url, restype=restype, comp=comp, version=self._config.version, @@ -183,7 +185,7 @@ async def get_properties( return deserialized - get_properties.metadata = {'url': "/"} # type: ignore + get_properties.metadata = {'url': "{url}"} # type: ignore @distributed_trace_async @@ -237,6 +239,7 @@ async def list_shares_segment( request = build_list_shares_segment_request( + url=self._config.url, comp=comp, version=self._config.version, prefix=prefix, @@ -272,5 +275,5 @@ async def list_shares_segment( return deserialized - list_shares_segment.metadata = {'url': "/"} # type: ignore + list_shares_segment.metadata = {'url': "{url}"} # type: ignore diff --git a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_generated/operations/_service_operations.py b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_generated/operations/_service_operations.py index 24809970ce3b..166f536c5dce 100644 --- a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_generated/operations/_service_operations.py +++ b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_generated/operations/_service_operations.py @@ -17,7 +17,7 @@ from azure.core.tracing.decorator import distributed_trace from .. import models as _models -from .._vendor import _convert_request +from .._vendor import _convert_request, _format_url_section if TYPE_CHECKING: # pylint: disable=unused-import,ungrouped-imports @@ -30,6 +30,7 @@ # fmt: off def build_set_properties_request( + url, # type: str **kwargs # type: Any ): # type: (...) -> HttpRequest @@ -41,7 +42,12 @@ def build_set_properties_request( accept = "application/xml" # Construct URL - _url = kwargs.pop("template_url", "/") + _url = kwargs.pop("template_url", "{url}") + path_format_arguments = { + "url": _SERIALIZER.url("url", url, 'str', skip_quote=True), + } + + _url = _format_url_section(_url, **path_format_arguments) # Construct parameters _query_parameters = kwargs.pop("params", {}) # type: Dict[str, Any] @@ -67,6 +73,7 @@ def build_set_properties_request( def build_get_properties_request( + url, # type: str **kwargs # type: Any ): # type: (...) -> HttpRequest @@ -77,7 +84,12 @@ def build_get_properties_request( accept = "application/xml" # Construct URL - _url = kwargs.pop("template_url", "/") + _url = kwargs.pop("template_url", "{url}") + path_format_arguments = { + "url": _SERIALIZER.url("url", url, 'str', skip_quote=True), + } + + _url = _format_url_section(_url, **path_format_arguments) # Construct parameters _query_parameters = kwargs.pop("params", {}) # type: Dict[str, Any] @@ -101,6 +113,7 @@ def build_get_properties_request( def build_list_shares_segment_request( + url, # type: str **kwargs # type: Any ): # type: (...) -> HttpRequest @@ -114,7 +127,12 @@ def build_list_shares_segment_request( accept = "application/xml" # Construct URL - _url = kwargs.pop("template_url", "/") + _url = kwargs.pop("template_url", "{url}") + path_format_arguments = { + "url": _SERIALIZER.url("url", url, 'str', skip_quote=True), + } + + _url = _format_url_section(_url, **path_format_arguments) # Construct parameters _query_parameters = kwargs.pop("params", {}) # type: Dict[str, Any] @@ -206,6 +224,7 @@ def set_properties( # pylint: disable=inconsistent-return-statements _content = self._serialize.body(storage_service_properties, 'StorageServiceProperties', is_xml=True) request = build_set_properties_request( + url=self._config.url, restype=restype, comp=comp, version=self._config.version, @@ -237,7 +256,7 @@ def set_properties( # pylint: disable=inconsistent-return-statements if cls: return cls(pipeline_response, None, response_headers) - set_properties.metadata = {'url': "/"} # type: ignore + set_properties.metadata = {'url': "{url}"} # type: ignore @distributed_trace @@ -277,6 +296,7 @@ def get_properties( request = build_get_properties_request( + url=self._config.url, restype=restype, comp=comp, version=self._config.version, @@ -309,7 +329,7 @@ def get_properties( return deserialized - get_properties.metadata = {'url': "/"} # type: ignore + get_properties.metadata = {'url': "{url}"} # type: ignore @distributed_trace @@ -364,6 +384,7 @@ def list_shares_segment( request = build_list_shares_segment_request( + url=self._config.url, comp=comp, version=self._config.version, prefix=prefix, @@ -399,5 +420,5 @@ def list_shares_segment( return deserialized - list_shares_segment.metadata = {'url': "/"} # type: ignore + list_shares_segment.metadata = {'url': "{url}"} # type: ignore diff --git a/sdk/storage/azure-storage-file-share/swagger/README.md b/sdk/storage/azure-storage-file-share/swagger/README.md index b53e1ea33a73..eece69a914db 100644 --- a/sdk/storage/azure-storage-file-share/swagger/README.md +++ b/sdk/storage/azure-storage-file-share/swagger/README.md @@ -119,7 +119,7 @@ directive: $["x-ms-parameterized-host"] = undefined; ``` -### Add url parameter to each operation and add it to the url +### Add url parameter to each operation and add url to the path ``` yaml directive: - from: swagger-document @@ -127,14 +127,21 @@ directive: transform: > for (const property in $) { - // Don't apply to service operations (where path is just '/') - if (property !== '/' && !property.startsWith('/?')) { - $[property]["parameters"].push({"$ref": "#/parameters/Url"}); - - var oldName = property; + $[property]["parameters"].push({"$ref": "#/parameters/Url"}); + + var oldName = property; + // For service operations (where the path is just '/') we need to + // remove the '/' at the begining to avoid having an extra '/' in + // the final URL. + if (property === '/' || property.startsWith('/?')) + { + var newName = '{url}' + property.substring(1); + } + else + { var newName = '{url}' + property; - $[newName] = $[oldName]; - delete $[oldName]; } + $[newName] = $[oldName]; + delete $[oldName]; } ``` diff --git a/sdk/storage/azure-storage-file-share/tests/test_share.py b/sdk/storage/azure-storage-file-share/tests/test_share.py index 40109da0817f..20975c68dc46 100644 --- a/sdk/storage/azure-storage-file-share/tests/test_share.py +++ b/sdk/storage/azure-storage-file-share/tests/test_share.py @@ -5,6 +5,7 @@ # Licensed under the MIT License. See License.txt in the project root for # license information. # -------------------------------------------------------------------------- +import os import time import unittest from datetime import datetime, timedelta @@ -12,29 +13,27 @@ import pytest import requests from azure.core.pipeline.transport import RequestsTransport -from azure.core.exceptions import ( - HttpResponseError, - ResourceNotFoundError, - ResourceExistsError) +from azure.core.exceptions import HttpResponseError, ResourceNotFoundError from azure.storage.fileshare import ( AccessPolicy, - ShareSasPermissions, + AccountSasPermissions, + ResourceTypes, ShareAccessTier, - ShareServiceClient, - ShareDirectoryClient, - ShareFileClient, ShareClient, - generate_share_sas, - ShareRootSquash, ShareProtocols) + ShareFileClient, + ShareSasPermissions, + ShareServiceClient, + ShareProtocols, + ShareRootSquash, + generate_account_sas, + generate_share_sas,) from devtools_testutils.storage import StorageTestCase, LogCaptured from settings.testcase import FileSharePreparer # ------------------------------------------------------------------------------ TEST_SHARE_PREFIX = 'share' - - # ------------------------------------------------------------------------------ class StorageShareTest(StorageTestCase): @@ -738,6 +737,30 @@ def test_list_shares_with_num_results_and_marker(self, storage_account_name, sto self.assertNamedItemInContainer(shares2, share_names[3]) self._delete_shares() + @pytest.mark.live_test_only + @FileSharePreparer() + def test_list_shares_account_sas(self, storage_account_name, storage_account_key): + self._setup(storage_account_name, storage_account_key) + share = self._create_share() + sas_token = generate_account_sas( + storage_account_name, + storage_account_key, + ResourceTypes(service=True), + AccountSasPermissions(list=True), + datetime.utcnow() + timedelta(hours=1), + ) + + # Act + fsc = ShareServiceClient(self.account_url(storage_account_name, "file"), credential=sas_token) + shares = list(fsc.list_shares()) + + # Assert + self.assertIsNotNone(shares) + self.assertGreaterEqual(len(shares), 1) + self.assertIsNotNone(shares[0]) + self.assertNamedItemInContainer(shares, share.share_name) + self._delete_shares() + @FileSharePreparer() def test_set_share_metadata(self, storage_account_name, storage_account_key): self._setup(storage_account_name, storage_account_key) diff --git a/sdk/storage/azure-storage-file-share/tests/test_share_async.py b/sdk/storage/azure-storage-file-share/tests/test_share_async.py index 5e611c1dd183..b97ef9e346c0 100644 --- a/sdk/storage/azure-storage-file-share/tests/test_share_async.py +++ b/sdk/storage/azure-storage-file-share/tests/test_share_async.py @@ -5,30 +5,29 @@ # Licensed under the MIT License. See License.txt in the project root for # license information. # -------------------------------------------------------------------------- +import os import time -import unittest from datetime import datetime, timedelta -import asyncio + import pytest import requests from azure.core.pipeline.transport import AioHttpTransport -from azure.core.pipeline.transport import AsyncioRequestsTransport from multidict import CIMultiDict, CIMultiDictProxy -from azure.core.exceptions import ( - HttpResponseError, - ResourceNotFoundError, - ResourceExistsError) +from azure.core.exceptions import HttpResponseError, ResourceNotFoundError from azure.storage.fileshare import ( AccessPolicy, - ShareSasPermissions, + AccountSasPermissions, + ResourceTypes, ShareAccessTier, + ShareProtocols, + ShareRootSquash, + ShareSasPermissions, + generate_account_sas, generate_share_sas, - ShareRootSquash, ShareProtocols ) from azure.storage.fileshare.aio import ( ShareServiceClient, - ShareDirectoryClient, ShareFileClient, ShareClient, ) @@ -37,8 +36,6 @@ from devtools_testutils.storage import LogCaptured # ------------------------------------------------------------------------------ TEST_SHARE_PREFIX = 'share' - - # ------------------------------------------------------------------------------ @@ -807,6 +804,33 @@ async def test_list_shares_with_num_results_and_marker_async(self, storage_accou self.assertNamedItemInContainer(shares2, share_names[3]) await self._delete_shares(prefix) + @pytest.mark.live_test_only + @FileSharePreparer() + @AsyncStorageTestCase.await_prepared_test + async def test_list_shares_account_sas(self, storage_account_name, storage_account_key): + self._setup(storage_account_name, storage_account_key) + share = await self._create_share() + sas_token = generate_account_sas( + storage_account_name, + storage_account_key, + ResourceTypes(service=True), + AccountSasPermissions(list=True), + datetime.utcnow() + timedelta(hours=1), + ) + + # Act + fsc = ShareServiceClient(self.account_url(storage_account_name, "file"), credential=sas_token) + shares = [] + async for s in fsc.list_shares(): + shares.append(s) + + # Assert + self.assertIsNotNone(shares) + self.assertGreaterEqual(len(shares), 1) + self.assertIsNotNone(shares[0]) + self.assertNamedItemInContainer(shares, share.share_name) + await self._delete_shares() + @FileSharePreparer() @AsyncStorageTestCase.await_prepared_test diff --git a/sdk/storage/azure-storage-queue/CHANGELOG.md b/sdk/storage/azure-storage-queue/CHANGELOG.md index c2e01a10060e..8c73864bbe1f 100644 --- a/sdk/storage/azure-storage-queue/CHANGELOG.md +++ b/sdk/storage/azure-storage-queue/CHANGELOG.md @@ -5,6 +5,8 @@ ### Features Added ### Bugs Fixed +- Fixed a bug, introduced in the previous beta release, that caused Authentication errors when attempting to use +an Account SAS with certain service level operations. ## 12.3.0b1 (2022-04-14) diff --git a/sdk/storage/azure-storage-queue/azure/storage/queue/_generated/aio/operations/_service_operations.py b/sdk/storage/azure-storage-queue/azure/storage/queue/_generated/aio/operations/_service_operations.py index 7a7cc0968a33..fd726d306b02 100644 --- a/sdk/storage/azure-storage-queue/azure/storage/queue/_generated/aio/operations/_service_operations.py +++ b/sdk/storage/azure-storage-queue/azure/storage/queue/_generated/aio/operations/_service_operations.py @@ -85,6 +85,7 @@ async def set_properties( # pylint: disable=inconsistent-return-statements _content = self._serialize.body(storage_service_properties, 'StorageServiceProperties', is_xml=True) request = build_set_properties_request( + url=self._config.url, restype=restype, comp=comp, version=self._config.version, @@ -117,7 +118,7 @@ async def set_properties( # pylint: disable=inconsistent-return-statements if cls: return cls(pipeline_response, None, response_headers) - set_properties.metadata = {'url': "/"} # type: ignore + set_properties.metadata = {'url': "{url}"} # type: ignore @distributed_trace_async @@ -160,6 +161,7 @@ async def get_properties( request = build_get_properties_request( + url=self._config.url, restype=restype, comp=comp, version=self._config.version, @@ -193,7 +195,7 @@ async def get_properties( return deserialized - get_properties.metadata = {'url': "/"} # type: ignore + get_properties.metadata = {'url': "{url}"} # type: ignore @distributed_trace_async @@ -237,6 +239,7 @@ async def get_statistics( request = build_get_statistics_request( + url=self._config.url, restype=restype, comp=comp, version=self._config.version, @@ -271,7 +274,7 @@ async def get_statistics( return deserialized - get_statistics.metadata = {'url': "/"} # type: ignore + get_statistics.metadata = {'url': "{url}"} # type: ignore @distributed_trace_async @@ -333,6 +336,7 @@ async def list_queues_segment( request = build_list_queues_segment_request( + url=self._config.url, comp=comp, version=self._config.version, prefix=prefix, @@ -370,5 +374,5 @@ async def list_queues_segment( return deserialized - list_queues_segment.metadata = {'url': "/"} # type: ignore + list_queues_segment.metadata = {'url': "{url}"} # type: ignore diff --git a/sdk/storage/azure-storage-queue/azure/storage/queue/_generated/operations/_service_operations.py b/sdk/storage/azure-storage-queue/azure/storage/queue/_generated/operations/_service_operations.py index 2786874db11b..ba6c8ac493bb 100644 --- a/sdk/storage/azure-storage-queue/azure/storage/queue/_generated/operations/_service_operations.py +++ b/sdk/storage/azure-storage-queue/azure/storage/queue/_generated/operations/_service_operations.py @@ -17,7 +17,7 @@ from azure.core.tracing.decorator import distributed_trace from .. import models as _models -from .._vendor import _convert_request +from .._vendor import _convert_request, _format_url_section if TYPE_CHECKING: # pylint: disable=unused-import,ungrouped-imports @@ -30,6 +30,7 @@ # fmt: off def build_set_properties_request( + url, # type: str **kwargs # type: Any ): # type: (...) -> HttpRequest @@ -42,7 +43,12 @@ def build_set_properties_request( accept = "application/xml" # Construct URL - _url = kwargs.pop("template_url", "/") + _url = kwargs.pop("template_url", "{url}") + path_format_arguments = { + "url": _SERIALIZER.url("url", url, 'str', skip_quote=True), + } + + _url = _format_url_section(_url, **path_format_arguments) # Construct parameters _query_parameters = kwargs.pop("params", {}) # type: Dict[str, Any] @@ -70,6 +76,7 @@ def build_set_properties_request( def build_get_properties_request( + url, # type: str **kwargs # type: Any ): # type: (...) -> HttpRequest @@ -81,7 +88,12 @@ def build_get_properties_request( accept = "application/xml" # Construct URL - _url = kwargs.pop("template_url", "/") + _url = kwargs.pop("template_url", "{url}") + path_format_arguments = { + "url": _SERIALIZER.url("url", url, 'str', skip_quote=True), + } + + _url = _format_url_section(_url, **path_format_arguments) # Construct parameters _query_parameters = kwargs.pop("params", {}) # type: Dict[str, Any] @@ -107,6 +119,7 @@ def build_get_properties_request( def build_get_statistics_request( + url, # type: str **kwargs # type: Any ): # type: (...) -> HttpRequest @@ -118,7 +131,12 @@ def build_get_statistics_request( accept = "application/xml" # Construct URL - _url = kwargs.pop("template_url", "/") + _url = kwargs.pop("template_url", "{url}") + path_format_arguments = { + "url": _SERIALIZER.url("url", url, 'str', skip_quote=True), + } + + _url = _format_url_section(_url, **path_format_arguments) # Construct parameters _query_parameters = kwargs.pop("params", {}) # type: Dict[str, Any] @@ -144,6 +162,7 @@ def build_get_statistics_request( def build_list_queues_segment_request( + url, # type: str **kwargs # type: Any ): # type: (...) -> HttpRequest @@ -158,7 +177,12 @@ def build_list_queues_segment_request( accept = "application/xml" # Construct URL - _url = kwargs.pop("template_url", "/") + _url = kwargs.pop("template_url", "{url}") + path_format_arguments = { + "url": _SERIALIZER.url("url", url, 'str', skip_quote=True), + } + + _url = _format_url_section(_url, **path_format_arguments) # Construct parameters _query_parameters = kwargs.pop("params", {}) # type: Dict[str, Any] @@ -256,6 +280,7 @@ def set_properties( # pylint: disable=inconsistent-return-statements _content = self._serialize.body(storage_service_properties, 'StorageServiceProperties', is_xml=True) request = build_set_properties_request( + url=self._config.url, restype=restype, comp=comp, version=self._config.version, @@ -288,7 +313,7 @@ def set_properties( # pylint: disable=inconsistent-return-statements if cls: return cls(pipeline_response, None, response_headers) - set_properties.metadata = {'url': "/"} # type: ignore + set_properties.metadata = {'url': "{url}"} # type: ignore @distributed_trace @@ -332,6 +357,7 @@ def get_properties( request = build_get_properties_request( + url=self._config.url, restype=restype, comp=comp, version=self._config.version, @@ -365,7 +391,7 @@ def get_properties( return deserialized - get_properties.metadata = {'url': "/"} # type: ignore + get_properties.metadata = {'url': "{url}"} # type: ignore @distributed_trace @@ -410,6 +436,7 @@ def get_statistics( request = build_get_statistics_request( + url=self._config.url, restype=restype, comp=comp, version=self._config.version, @@ -444,7 +471,7 @@ def get_statistics( return deserialized - get_statistics.metadata = {'url': "/"} # type: ignore + get_statistics.metadata = {'url': "{url}"} # type: ignore @distributed_trace @@ -507,6 +534,7 @@ def list_queues_segment( request = build_list_queues_segment_request( + url=self._config.url, comp=comp, version=self._config.version, prefix=prefix, @@ -544,5 +572,5 @@ def list_queues_segment( return deserialized - list_queues_segment.metadata = {'url': "/"} # type: ignore + list_queues_segment.metadata = {'url': "{url}"} # type: ignore diff --git a/sdk/storage/azure-storage-queue/swagger/README.md b/sdk/storage/azure-storage-queue/swagger/README.md index 682a2ad81655..138348a8eedb 100644 --- a/sdk/storage/azure-storage-queue/swagger/README.md +++ b/sdk/storage/azure-storage-queue/swagger/README.md @@ -96,7 +96,7 @@ directive: $["x-ms-parameterized-host"] = undefined; ``` -### Add url parameter to each operation and add it to the url +### Add url parameter to each operation and add url to the path ``` yaml directive: - from: swagger-document @@ -104,14 +104,21 @@ directive: transform: > for (const property in $) { - // Don't apply to service operations (where path is just '/') - if (property !== '/' && !property.startsWith('/?')) { - $[property]["parameters"].push({"$ref": "#/parameters/Url"}); - - var oldName = property; + $[property]["parameters"].push({"$ref": "#/parameters/Url"}); + + var oldName = property; + // For service operations (where the path is just '/') we need to + // remove the '/' at the begining to avoid having an extra '/' in + // the final URL. + if (property === '/' || property.startsWith('/?')) + { + var newName = '{url}' + property.substring(1); + } + else + { var newName = '{url}' + property; - $[newName] = $[oldName]; - delete $[oldName]; } + $[newName] = $[oldName]; + delete $[oldName]; } ``` diff --git a/sdk/storage/azure-storage-queue/tests/test_queue.py b/sdk/storage/azure-storage-queue/tests/test_queue.py index cca4490b0548..6273d2bc3ace 100644 --- a/sdk/storage/azure-storage-queue/tests/test_queue.py +++ b/sdk/storage/azure-storage-queue/tests/test_queue.py @@ -6,20 +6,15 @@ # license information. # -------------------------------------------------------------------------- -from collections import namedtuple -import unittest import pytest -import sys - -from azure.core.credentials import AzureSasCredential -from dateutil.tz import tzutc +import unittest from datetime import ( datetime, timedelta, date, ) -from devtools_testutils.storage import StorageTestCase -from azure.mgmt.storage.models import Endpoints + +from azure.core.credentials import AzureSasCredential from azure.core.pipeline.transport import RequestsTransport from azure.core.exceptions import ( HttpResponseError, @@ -28,16 +23,17 @@ ClientAuthenticationError) from azure.storage.queue import ( - QueueServiceClient, - QueueClient, - QueueSasPermissions, AccessPolicy, - ResourceTypes, AccountSasPermissions, + ResourceTypes, + QueueClient, + QueueSasPermissions, + QueueServiceClient, generate_account_sas, generate_queue_sas ) +from devtools_testutils.storage import StorageTestCase from settings.testcase import QueuePreparer # ------------------------------------------------------------------------------ @@ -198,6 +194,29 @@ def test_list_queues_with_metadata(self, storage_account_name, storage_account_k self.assertEqual(len(listed_queue.metadata), 2) self.assertEqual(listed_queue.metadata['val1'], 'test') + @pytest.mark.live_test_only + @QueuePreparer() + def test_list_queues_account_sas(self, storage_account_name, storage_account_key): + # Action + qsc = QueueServiceClient(self.account_url(storage_account_name, "queue"), storage_account_key) + queue_client = self._get_queue_reference(qsc) + queue_client.create_queue() + sas_token = generate_account_sas( + storage_account_name, + storage_account_key, + ResourceTypes(service=True), + AccountSasPermissions(list=True), + datetime.utcnow() + timedelta(hours=1) + ) + + # Act + qsc = QueueServiceClient(self.account_url(storage_account_name, "queue"), credential=sas_token) + queues = list(qsc.list_queues()) + + # Assert + self.assertIsNotNone(queues) + assert len(queues) >= 1 + @QueuePreparer() def test_set_queue_metadata(self, storage_account_name, storage_account_key): # Action diff --git a/sdk/storage/azure-storage-queue/tests/test_queue_async.py b/sdk/storage/azure-storage-queue/tests/test_queue_async.py index 34b85eb9477f..0a67b04fe15a 100644 --- a/sdk/storage/azure-storage-queue/tests/test_queue_async.py +++ b/sdk/storage/azure-storage-queue/tests/test_queue_async.py @@ -5,41 +5,39 @@ # Licensed under the MIT License. See License.txt in the project root for # license information. # -------------------------------------------------------------------------- -import unittest -import pytest -import asyncio -from azure.core.credentials import AzureSasCredential -from dateutil.tz import tzutc +import pytest +import unittest from datetime import ( datetime, timedelta, date, ) -from multidict import CIMultiDict, CIMultiDictProxy -from devtools_testutils.storage.aio import AsyncStorageTestCase + +from azure.core.credentials import AzureSasCredential +from azure.core.pipeline.transport import AioHttpTransport from azure.core.exceptions import ( HttpResponseError, ResourceNotFoundError, ResourceExistsError, ClientAuthenticationError) +from multidict import CIMultiDict, CIMultiDictProxy -from azure.core.pipeline.transport import AioHttpTransport from azure.storage.queue import ( - QueueSasPermissions, AccessPolicy, - ResourceTypes, AccountSasPermissions, + ResourceTypes, + QueueSasPermissions, generate_account_sas, generate_queue_sas ) from azure.storage.queue.aio import QueueServiceClient, QueueClient +from devtools_testutils.storage.aio import AsyncStorageTestCase from settings.testcase import QueuePreparer # ------------------------------------------------------------------------------ TEST_QUEUE_PREFIX = 'pyqueueasync' - # ------------------------------------------------------------------------------ @@ -358,6 +356,32 @@ async def test_list_queues_with_metadata(self, storage_account_name, storage_acc self.assertEqual(len(listed_queue.metadata), 2) self.assertEqual(listed_queue.metadata['val1'], 'test') + @pytest.mark.live_test_only + @QueuePreparer() + @AsyncStorageTestCase.await_prepared_test + async def test_list_queues_account_sas(self, storage_account_name, storage_account_key): + # Action + qsc = QueueServiceClient(self.account_url(storage_account_name, "queue"), storage_account_key) + queue_client = self._get_queue_reference(qsc) + await queue_client.create_queue() + sas_token = generate_account_sas( + storage_account_name, + storage_account_key, + ResourceTypes(service=True), + AccountSasPermissions(list=True), + datetime.utcnow() + timedelta(hours=1) + ) + + # Act + qsc = QueueServiceClient(self.account_url(storage_account_name, "queue"), credential=sas_token) + queues = [] + async for q in qsc.list_queues(): + queues.append(q) + + # Assert + self.assertIsNotNone(queues) + assert len(queues) >= 1 + @QueuePreparer() @AsyncStorageTestCase.await_prepared_test async def test_set_queue_metadata(self, storage_account_name, storage_account_key):