Skip to content
Merged

tag sas #12258

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -383,12 +383,6 @@ def list_containers(
:param bool include_metadata:
Specifies that container metadata to be returned in the response.
The default value is `False`.
:keyword bool include_deleted:
Specifies that deleted containers to be returned in the response. This is for container restore enabled
account. The default value is `False`.

.. versionadded:: 12.4.0

:keyword int results_per_page:
The maximum number of container names to retrieve per API
call. If the request does not specify the server will return up to 5,000 items.
Expand All @@ -407,9 +401,6 @@ def list_containers(
:caption: Listing the containers in the blob service.
"""
include = ['metadata'] if include_metadata else []
include_deleted = kwargs.pop('include_deleted', None)
if include_deleted:
include.append("deleted")

timeout = kwargs.pop('timeout', None)
results_per_page = kwargs.pop('results_per_page', None)
Expand All @@ -436,8 +427,8 @@ def find_blobs_by_tags(self, filter_expression, **kwargs):

:param str filter_expression:
The expression to find blobs whose tags matches the specified condition.
eg. "yourtagname='firsttag' and yourtagname2='secondtag'"
To specify a container, eg. "@container=’containerName’ and Name = ‘C’"
eg. "\"yourtagname\"='firsttag' and \"yourtagname2\"='secondtag'"
To specify a container, eg. "@container=’containerName’ and \"Name\"=‘C’"
:keyword int results_per_page:
The max result per page when paginating.
:keyword int timeout:
Expand Down Expand Up @@ -566,7 +557,7 @@ def delete_container(
**kwargs)

@distributed_trace
def undelete_container(self, deleted_container_name, deleted_container_version, new_name=None, **kwargs):
def _undelete_container(self, deleted_container_name, deleted_container_version, new_name=None, **kwargs):
# type: (str, str, str, **Any) -> ContainerClient
"""Restores soft-deleted container.

Expand Down
29 changes: 23 additions & 6 deletions sdk/storage/azure-storage-blob/azure/storage/blob/_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1054,18 +1054,26 @@ class ContainerSasPermissions(object):
:param bool delete:
Delete any blob in the container. Note: You cannot grant permissions to
delete a container with a container SAS. Use an account SAS instead.
:param bool delete_previous_version:
Delete the previous blob version for the versioning enabled storage account.
:param bool list:
List blobs in the container.
:param bool tag:
Set or get tags on the blobs in the container.
"""
def __init__(self, read=False, write=False, delete=False, list=False): # pylint: disable=redefined-builtin
def __init__(self, read=False, write=False, delete=False, list=False, delete_previous_version=False, tag=False): # pylint: disable=redefined-builtin
self.read = read
self.write = write
self.delete = delete
self.list = list
self.delete_previous_version = delete_previous_version
self.tag = tag
self._str = (('r' if self.read else '') +
('w' if self.write else '') +
('d' if self.delete else '') +
('l' if self.list else ''))
('x' if self.delete_previous_version else '') +
('l' if self.list else '') +
('t' if self.tag else ''))

def __str__(self):
return self._str
Expand All @@ -1087,7 +1095,10 @@ def from_string(cls, permission):
p_write = 'w' in permission
p_delete = 'd' in permission
p_list = 'l' in permission
parsed = cls(p_read, p_write, p_delete, p_list)
p_delete_previous_version = 'x' in permission
p_tag = 't' in permission
parsed = cls(read=p_read, write=p_write, delete=p_delete, list=p_list,
delete_previous_version=p_delete_previous_version, tag=p_tag)
parsed._str = permission # pylint: disable = protected-access
return parsed

Expand All @@ -1111,21 +1122,25 @@ class BlobSasPermissions(object):
Delete the blob.
:param bool delete_previous_version:
Delete the previous blob version for the versioning enabled storage account.
:param bool tag:
Set or get tags on the blob.
"""
def __init__(self, read=False, add=False, create=False, write=False,
delete=False, delete_previous_version=False):
delete=False, delete_previous_version=False, tag=True):
self.read = read
self.add = add
self.create = create
self.write = write
self.delete = delete
self.delete_previous_version = delete_previous_version
self.tag = tag
self._str = (('r' if self.read else '') +
('a' if self.add else '') +
('c' if self.create else '') +
('w' if self.write else '') +
('d' if self.delete else '') +
('x' if self.delete_previous_version else ''))
('x' if self.delete_previous_version else '') +
('t' if self.tag else ''))

def __str__(self):
return self._str
Expand All @@ -1149,8 +1164,10 @@ def from_string(cls, permission):
p_write = 'w' in permission
p_delete = 'd' in permission
p_delete_previous_version = 'x' in permission
p_tag = 't' in permission

parsed = cls(p_read, p_add, p_create, p_write, p_delete, p_delete_previous_version)
parsed = cls(read=p_read, add=p_add, create=p_create, write=p_write, delete=p_delete,
delete_previous_version=p_delete_previous_version, tag=p_tag)
parsed._str = permission # pylint: disable = protected-access
return parsed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# Licensed under the MIT License. See License.txt in the project root for
# license information.
# --------------------------------------------------------------------------

# pylint: disable=too-many-instance-attributes
from enum import Enum


Expand Down Expand Up @@ -324,10 +324,15 @@ class AccountSasPermissions(object):
Valid for the following Object resource types only: queue messages.
:param bool process:
Valid for the following Object resource type only: queue messages.
:param bool tag:
To enable set or get tags on the blobs in the container.
:param bool filter_by_tags:
To enable get blobs by tags, this should be used together with list permission.
"""
def __init__(self, read=False, write=False, delete=False,
list=False, # pylint: disable=redefined-builtin
add=False, create=False, update=False, process=False, delete_previous_version=False):
add=False, create=False, update=False, process=False, delete_previous_version=False, tag=False,
filter_by_tags=False):
self.read = read
self.write = write
self.delete = delete
Expand All @@ -337,6 +342,8 @@ def __init__(self, read=False, write=False, delete=False,
self.create = create
self.update = update
self.process = process
self.tag = tag
self.filter_by_tags = filter_by_tags
self._str = (('r' if self.read else '') +
('w' if self.write else '') +
('d' if self.delete else '') +
Expand All @@ -345,7 +352,10 @@ def __init__(self, read=False, write=False, delete=False,
('a' if self.add else '') +
('c' if self.create else '') +
('u' if self.update else '') +
('p' if self.process else ''))
('p' if self.process else '') +
('f' if self.filter_by_tags else '') +
('t' if self.tag else '')
)

def __str__(self):
return self._str
Expand All @@ -360,7 +370,7 @@ def from_string(cls, permission):

:param str permission: Specify permissions in
the string with the first letter of the word.
:return: A AccountSasPermissions object
:return: An AccountSasPermissions object
:rtype: ~azure.storage.blob.AccountSasPermissions
"""
p_read = 'r' in permission
Expand All @@ -372,8 +382,11 @@ def from_string(cls, permission):
p_create = 'c' in permission
p_update = 'u' in permission
p_process = 'p' in permission

parsed = cls(p_read, p_write, p_delete, p_delete_previous_version, p_list, p_add, p_create, p_update, p_process)
p_tag = 't' in permission
p_filter_by_tags = 'f' in permission
parsed = cls(read=p_read, write=p_write, delete=p_delete, delete_previous_version=p_delete_previous_version,
list=p_list, add=p_add, create=p_create, update=p_update, process=p_process, tag=p_tag,
filter_by_tags=p_filter_by_tags)
parsed._str = permission # pylint: disable = protected-access
return parsed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -337,12 +337,6 @@ def list_containers(
:param bool include_metadata:
Specifies that container metadata to be returned in the response.
The default value is `False`.
:keyword bool include_deleted:
Specifies that deleted containers to be returned in the response. This is for container restore enabled
account. The default value is `False`.

.. versionadded:: 12.4.0

:keyword int results_per_page:
The maximum number of container names to retrieve per API
call. If the request does not specify the server will return up to 5,000 items.
Expand All @@ -361,9 +355,6 @@ def list_containers(
:caption: Listing the containers in the blob service.
"""
include = ['metadata'] if include_metadata else []
include_deleted = kwargs.pop('include_deleted', None)
if include_deleted:
include.append("deleted")
timeout = kwargs.pop('timeout', None)
results_per_page = kwargs.pop('results_per_page', None)
command = functools.partial(
Expand All @@ -389,8 +380,8 @@ def find_blobs_by_tags(self, filter_expression, **kwargs):

:param str filter_expression:
The expression to find blobs whose tags matches the specified condition.
eg. "yourtagname='firsttag' and yourtagname2='secondtag'"
To specify a container, eg. "@container=’containerName’ and Name = ‘C’"
eg. "\"yourtagname\"='firsttag' and \"yourtagname2\"='secondtag'"
To specify a container, eg. "@container=’containerName’ and \"Name\"=‘C’"
:keyword int results_per_page:
The max result per page when paginating.
:keyword int timeout:
Expand Down Expand Up @@ -519,7 +510,7 @@ async def delete_container(
**kwargs)

@distributed_trace_async
async def undelete_container(self, deleted_container_name, deleted_container_version, new_name=None, **kwargs):
async def _undelete_container(self, deleted_container_name, deleted_container_version, new_name=None, **kwargs):
# type: (str, str, str, **Any) -> ContainerClient
"""Restores soft-deleted container.

Expand Down
73 changes: 71 additions & 2 deletions sdk/storage/azure-storage-blob/tests/test_blob_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
# license information.
# --------------------------------------------------------------------------
import os
import uuid
from datetime import datetime, timedelta
from enum import Enum

import pytest

try:
from urllib.parse import quote
except ImportError:
Expand All @@ -17,7 +20,8 @@
ResourceExistsError)
from azure.storage.blob import (
BlobServiceClient,
BlobBlock)
BlobBlock, generate_account_sas, ResourceTypes, AccountSasPermissions, generate_container_sas,
ContainerSasPermissions, BlobClient, generate_blob_sas, BlobSasPermissions)

#------------------------------------------------------------------------------

Expand Down Expand Up @@ -279,4 +283,69 @@ def test_filter_blobs(self, resource_group, location, storage_account, storage_a

self.assertEqual(2, len(items_on_page1))
self.assertEqual(2, len(items_on_page2))

@pytest.mark.live_test_only
@GlobalStorageAccountPreparer()
def test_filter_blobs_using_account_sas(self, resource_group, location, storage_account, storage_account_key):
token = generate_account_sas(
storage_account.name,
storage_account_key,
ResourceTypes(service=True, container=True, object=True),
AccountSasPermissions(write=True, list=True, read=True, delete_previous_version=True, tag=True,
filter_by_tags=True),
datetime.utcnow() + timedelta(hours=1),
)
self._setup(storage_account, token)

tags = {"year": '1000', "tag2": "secondtag", "tag3": "thirdtag", "habitat_type": 'Shallow Lowland Billabongs'}
blob_client, _ = self._create_block_blob(tags=tags, container_name=self.container_name)
blob_client.set_blob_tags(tags=tags)
tags_on_blob = blob_client.get_blob_tags()
self.assertEqual(len(tags_on_blob), len(tags))

# To filter in a specific container use:
# where = "@container='{}' and tag1='1000' and tag2 = 'secondtag'".format(container_name1)
where = "\"year\"='1000' and tag2 = 'secondtag' and tag3='thirdtag'"

blob_list = self.bsc.find_blobs_by_tags(filter_expression=where, results_per_page=2).by_page()
first_page = next(blob_list)
items_on_page1 = list(first_page)
self.assertEqual(1, len(items_on_page1))

@pytest.mark.live_test_only
@GlobalStorageAccountPreparer()
def test_set_blob_tags_using_blob_sas(self, resource_group, location, storage_account, storage_account_key):
token = generate_account_sas(
storage_account.name,
storage_account_key,
ResourceTypes(service=True, container=True, object=True),
AccountSasPermissions(write=True, list=True, read=True, delete_previous_version=True, tag=True,
filter_by_tags=True),
datetime.utcnow() + timedelta(hours=1),
)
self._setup(storage_account, token)

tags = {"year": '1000', "tag2": "secondtag", "tag3": "thirdtag", "habitat_type": 'Shallow Lowland Billabongs'}
blob_client, _ = self._create_block_blob(tags=tags, container_name=self.container_name)
token1 = generate_blob_sas(
storage_account.name,
self.container_name,
blob_client.blob_name,
account_key=storage_account_key,
permission=BlobSasPermissions(delete_previous_version=True, tag=True),
expiry=datetime.utcnow() + timedelta(hours=1),
)
blob_client=BlobClient.from_blob_url(blob_client.url, token1)
blob_client.set_blob_tags(tags=tags)
tags_on_blob = blob_client.get_blob_tags()
self.assertEqual(len(tags_on_blob), len(tags))

# To filter in a specific container use:
# where = "@container='{}' and tag1='1000' and tag2 = 'secondtag'".format(container_name1)
where = "\"year\"='1000' and tag2 = 'secondtag' and tag3='thirdtag'"

blob_list = self.bsc.find_blobs_by_tags(filter_expression=where, results_per_page=2).by_page()
first_page = next(blob_list)
items_on_page1 = list(first_page)
self.assertEqual(1, len(items_on_page1))
#------------------------------------------------------------------------------
15 changes: 9 additions & 6 deletions sdk/storage/azure-storage-blob/tests/test_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -723,6 +723,7 @@ def test_delete_container_with_lease_id(self, resource_group, location, storage_
@GlobalStorageAccountPreparer()
def test_undelete_container(self, resource_group, location, storage_account, storage_account_key):
# container soft delete should enabled by SRP call or use armclient, so make this test as playback only.
pytest.skip('This will be added back along with STG74 features')

bsc = BlobServiceClient(self.account_url(storage_account, "blob"), storage_account_key)
container_client = self._create_container(bsc)
Expand All @@ -740,8 +741,8 @@ def test_undelete_container(self, resource_group, location, storage_account, sto
for container in container_list:
# find the deleted container and restore it
if container.deleted and container.name == container_client.container_name:
restored_ctn_client = bsc.undelete_container(container.name, container.version,
new_name="restored" + str(restored_version))
restored_ctn_client = bsc._undelete_container(container.name, container.version,
new_name="restored" + str(restored_version))
restored_version += 1

# to make sure the deleted container is restored
Expand All @@ -752,6 +753,7 @@ def test_undelete_container(self, resource_group, location, storage_account, sto
@GlobalStorageAccountPreparer()
def test_restore_to_existing_container(self, resource_group, location, storage_account, storage_account_key):
# container soft delete should enabled by SRP call or use armclient, so make this test as playback only.
pytest.skip('This will be added back along with STG74 features')

bsc = BlobServiceClient(self.account_url(storage_account, "blob"), storage_account_key)
# get an existing container
Expand All @@ -771,14 +773,15 @@ def test_restore_to_existing_container(self, resource_group, location, storage_a
# find the deleted container and restore it
if container.deleted and container.name == container_client.container_name:
with self.assertRaises(HttpResponseError):
bsc.undelete_container(container.name, container.version,
new_name=existing_container_client.container_name)
bsc._undelete_container(container.name, container.version,
new_name=existing_container_client.container_name)

@pytest.mark.live_test_only # sas token is dynamically generated
@pytest.mark.playback_test_only # we need container soft delete enabled account
@GlobalStorageAccountPreparer()
def test_restore_with_sas(self, resource_group, location, storage_account, storage_account_key):
# container soft delete should enabled by SRP call or use armclient, so make this test as playback only.
pytest.skip('This will be added back along with STG74 features')
token = generate_account_sas(
storage_account.name,
storage_account_key,
Expand All @@ -800,8 +803,8 @@ def test_restore_with_sas(self, resource_group, location, storage_account, stora
for container in container_list:
# find the deleted container and restore it
if container.deleted and container.name == container_client.container_name:
restored_ctn_client = bsc.undelete_container(container.name, container.version,
new_name="restored" + str(restored_version))
restored_ctn_client = bsc._undelete_container(container.name, container.version,
new_name="restored" + str(restored_version))
restored_version += 1

# to make sure the deleted container is restored
Expand Down
Loading