Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Storage] BREAKING CHANGE: Fix #22261 #22240: az storage blob snapshot/update/exists/delete/undelete/delete-batch: Migrate to track2 sdk, az storage blob snapshot now only returns version info instead of all blob properties #22309

Merged
merged 18 commits into from
May 19, 2022
Merged
Show file tree
Hide file tree
Changes from 14 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
13 changes: 13 additions & 0 deletions src/azure-cli/azure/cli/command_modules/storage/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -789,6 +789,19 @@
text: az storage blob delete -c mycontainer -n MyBlob --account-name mystorageaccount --auth-mode login
"""

helps['storage blob undelete'] = """
type: command
short-summary: Restore soft deleted blob or snapshot.
long-summary: >
Operation will only be successful if used within the specified number of days set in the delete retention policy.
Attempting to undelete a blob or snapshot that is not soft deleted will succeed without any changes.
examples:
- name: Undelete a blob.
text: az storage blob undelete -c mycontainer -n MyBlob
- name: Undelete a blob using login credentials.
text: az storage blob undelete -c mycontainer -n MyBlob --account-name mystorageaccount --auth-mode login
"""

helps['storage blob delete-batch'] = """
type: command
short-summary: Delete blobs from a blob container recursively.
Expand Down
27 changes: 24 additions & 3 deletions src/azure-cli/azure/cli/command_modules/storage/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -870,11 +870,16 @@ def load_arguments(self, _): # pylint: disable=too-many-locals, too-many-statem
'configured to allow overrides. Otherwise an error will be raised.')

with self.argument_context('storage blob update') as c:
t_blob_content_settings = self.get_sdk('blob.models#ContentSettings')
c.register_blob_arguments()
c.register_precondition_options()
t_blob_content_settings = self.get_sdk('_models#ContentSettings', resource_type=ResourceType.DATA_STORAGE_BLOB)
c.register_content_settings_argument(t_blob_content_settings, update=True)
c.extra('lease', options_list=['--lease-id'], help='Required if the blob has an active lease.')

with self.argument_context('storage blob exists') as c:
c.argument('blob_name', required=True)
c.register_blob_arguments()
c.extra('snapshot', help='The snapshot parameter is an opaque DateTime value that, when present, '
'specifies the snapshot.')

with self.argument_context('storage blob url') as c:
from ._validators import get_not_none_validator
Expand All @@ -884,6 +889,11 @@ def load_arguments(self, _): # pylint: disable=too-many-locals, too-many-statem
c.extra('snapshot', help='An string value that uniquely identifies the snapshot. The value of this query '
'parameter indicates the snapshot version.')

with self.argument_context('storage blob snapshot') as c:
c.register_blob_arguments()
c.register_precondition_options()
c.extra('lease', options_list=['--lease-id'], help='Required if the blob has an active lease.')

with self.argument_context('storage blob set-tier') as c:
from azure.cli.command_modules.storage._validators import (blob_rehydrate_priority_validator)
c.register_blob_arguments()
Expand Down Expand Up @@ -1047,7 +1057,18 @@ def load_arguments(self, _): # pylint: disable=too-many-locals, too-many-statem

with self.argument_context('storage blob delete') as c:
from .sdkutil import get_delete_blob_snapshot_type_names
c.argument('delete_snapshots', arg_type=get_enum_type(get_delete_blob_snapshot_type_names()))
c.register_blob_arguments()
c.register_precondition_options()
c.argument('delete_snapshots', arg_type=get_enum_type(get_delete_blob_snapshot_type_names()),
help='Required if the blob has associated snapshots. '
'Values include: "only": Deletes only the blobs snapshots. '
'"include": Deletes the blob along with all snapshots.')
c.extra('lease', options_list='--lease-id', help='Required if the blob has an active lease.')
c.extra('snapshot', help='The snapshot parameter is an opaque DateTime value that, when present, '
'specifies the blob snapshot to delete.')

with self.argument_context('storage blob undelete') as c:
c.register_blob_arguments()

with self.argument_context('storage blob delete-batch') as c:
c.ignore('source_container_name')
Expand Down
29 changes: 15 additions & 14 deletions src/azure-cli/azure/cli/command_modules/storage/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,10 +317,12 @@ def get_custom_sdk(custom_module, client_factory, resource_type=ResourceType.DAT
custom_command_type=get_custom_sdk('blob', client_factory=cf_blob_client,
resource_type=ResourceType.DATA_STORAGE_BLOB)) as g:
from ._transformers import (transform_blob_list_output, transform_blob_json_output,
transform_blob_upload_output, transform_url_without_encode)
from ._format import transform_blob_output
transform_blob_upload_output, transform_url_without_encode,
create_boolean_result_output_transformer)
from ._format import transform_blob_output, transform_boolean_for_table
from ._exception_handler import file_related_exception_handler
from ._validators import process_blob_upload_batch_parameters, process_blob_download_batch_parameters
from ._validators import (process_blob_upload_batch_parameters, process_blob_download_batch_parameters,
process_blob_delete_batch_parameters)
g.storage_custom_command_oauth('copy start', 'copy_blob')
g.storage_custom_command_oauth('show', 'show_blob_v2', transform=transform_blob_json_output,
table_transformer=transform_blob_output,
Expand Down Expand Up @@ -349,6 +351,16 @@ def get_custom_sdk(custom_module, client_factory, resource_type=ResourceType.DAT
exception_handler=file_related_exception_handler)
g.storage_custom_command_oauth('url', 'create_blob_url', client_factory=cf_blob_service,
transform=transform_url_without_encode)
g.storage_command_oauth('snapshot', 'create_snapshot')
g.storage_command_oauth('update', 'set_http_headers')
g.storage_custom_command_oauth('exists', 'exists', client_factory=cf_blob_service,
transform=create_boolean_result_output_transformer('exists'))
g.storage_command_oauth('delete', 'delete_blob')
g.storage_command_oauth('undelete', 'undelete_blob',
transform=create_boolean_result_output_transformer('undeleted'),
table_transformer=transform_boolean_for_table)
g.storage_custom_command_oauth('delete-batch', 'storage_blob_delete_batch', client_factory=cf_blob_service,
validator=process_blob_delete_batch_parameters)

blob_service_custom_sdk = get_custom_sdk('blob', client_factory=cf_blob_service,
resource_type=ResourceType.DATA_STORAGE_BLOB)
Expand Down Expand Up @@ -382,20 +394,9 @@ def get_custom_sdk(custom_module, client_factory, resource_type=ResourceType.DAT
# g.storage_command_oauth(
# 'download', 'get_blob_to_path', table_transformer=transform_blob_output,
# exception_handler=file_related_exception_handler)
g.storage_command_oauth('snapshot', 'snapshot_blob')
g.storage_command_oauth('update', 'set_blob_properties')
g.storage_command_oauth(
'exists', 'exists', transform=create_boolean_result_output_transformer('exists'))
g.storage_command_oauth('delete', 'delete_blob')
g.storage_command_oauth('undelete', 'undelete_blob',
transform=create_boolean_result_output_transformer(
'undeleted'),
table_transformer=transform_boolean_for_table, min_api='2017-07-29')
# g.storage_custom_command_oauth('download-batch', 'storage_blob_download_batch',
# validator=process_blob_download_batch_parameters,
# exception_handler=file_related_exception_handler)
calvinhzy marked this conversation as resolved.
Show resolved Hide resolved
g.storage_custom_command_oauth('delete-batch', 'storage_blob_delete_batch',
validator=process_blob_delete_batch_parameters)
g.storage_command_oauth(
'metadata show', 'get_blob_metadata', exception_handler=show_exception_handler)
g.storage_command_oauth('metadata update', 'set_blob_metadata')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
filter_none, collect_blobs, collect_blob_objects, collect_files,
mkdir_p, guess_content_type, normalize_blob_file_path,
check_precondition_success)
from azure.core.exceptions import ResourceExistsError, ResourceModifiedError
from azure.core.exceptions import ResourceExistsError, ResourceModifiedError, HttpResponseError

from knack.log import get_logger
from knack.util import CLIError
Expand Down Expand Up @@ -119,7 +119,6 @@ def list_container_rm(cmd, client, resource_group_name, account_name, include_de


def container_rm_exists(client, resource_group_name, account_name, container_name):
from azure.core.exceptions import HttpResponseError
try:
container = client.get(resource_group_name=resource_group_name,
account_name=account_name, container_name=container_name)
Expand Down Expand Up @@ -713,20 +712,25 @@ def show_blob(cmd, client, container_name, blob_name, snapshot=None, lease_id=No
def storage_blob_delete_batch(client, source, source_container_name, pattern=None, lease_id=None,
delete_snapshots=None, if_modified_since=None, if_unmodified_since=None, if_match=None,
if_none_match=None, timeout=None, dryrun=False):
container_client = client.get_container_client(source_container_name)

@check_precondition_success
def _delete_blob(blob_name):
delete_blob_args = {
'container_name': source_container_name,
'blob_name': blob_name,
'lease_id': lease_id,
'blob': blob_name,
'lease': lease_id,
'delete_snapshots': delete_snapshots,
'if_modified_since': if_modified_since,
'if_unmodified_since': if_unmodified_since,
'if_match': if_match,
'if_none_match': if_none_match,
'timeout': timeout
}
return client.delete_blob(**delete_blob_args)
try:
container_client.delete_blob(**delete_blob_args)
return blob_name
except HttpResponseError:
pass
calvinhzy marked this conversation as resolved.
Show resolved Hide resolved

source_blobs = list(collect_blob_objects(client, source_container_name, pattern))

Expand All @@ -736,8 +740,8 @@ def _delete_blob(blob_name):
if_modified_since_utc = if_modified_since.replace(tzinfo=timezone.utc) if if_modified_since else None
if_unmodified_since_utc = if_unmodified_since.replace(tzinfo=timezone.utc) if if_unmodified_since else None
for blob in source_blobs:
if not if_modified_since or blob[1].properties.last_modified >= if_modified_since_utc:
if not if_unmodified_since or blob[1].properties.last_modified <= if_unmodified_since_utc:
if not if_modified_since or blob[1].last_modified >= if_modified_since_utc:
if not if_unmodified_since or blob[1].last_modified <= if_unmodified_since_utc:
delete_blobs.append(blob[0])
logger.warning('delete action: from %s', source)
logger.warning(' pattern %s', pattern)
Expand All @@ -748,7 +752,7 @@ def _delete_blob(blob_name):
logger.warning(' - %s', blob)
return []

results = [result for include, result in (_delete_blob(blob[0]) for blob in source_blobs) if include]
results = [result for (include, result) in (_delete_blob(blob[0]) for blob in source_blobs) if result]
num_failures = len(source_blobs) - len(results)
if num_failures:
logger.warning('%s of %s blobs not deleted due to "Failed Precondition"', num_failures, len(source_blobs))
Expand Down Expand Up @@ -945,3 +949,11 @@ def copy_blob(client, source_url, metadata=None, **kwargs):
if not kwargs['requires_sync']:
kwargs.pop('requires_sync')
return client.start_copy_from_url(source_url=source_url, metadata=metadata, incremental_copy=False, **kwargs)


def exists(client, container_name, blob_name, snapshot, timeout):
if blob_name:
client = client.get_blob_client(container=container_name, blob=blob_name, snapshot=snapshot)
else:
client = client.get_container_client(container=container_name)
return client.exists(timeout=timeout)
Loading