Skip to content

Commit bef7a69

Browse files
authored
Enable adding destination path to storage upload/copy batch commands. (#5620)
* added destination-path and normalize paths normalized paths for file copy batch as well * tests style fix * feedback and fix tests * renamed util normalize-path function * feedback changes
1 parent d3aebfd commit bef7a69

File tree

6 files changed

+151
-44
lines changed

6 files changed

+151
-44
lines changed

src/command_modules/azure-cli-storage/HISTORY.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
Release History
44
===============
55

6+
2.0.26
7+
++++++
8+
* Enabled specifying destination-path/prefix to blobs in batch upload and copy commands.
9+
610
2.0.25
711
++++++
812
* Added `storage blob service-properties delete-policy` and `storage blob undelete` commands to enable soft-delete.

src/command_modules/azure-cli-storage/azure/cli/command_modules/storage/operations/blob.py

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@
55

66
from __future__ import print_function
77

8+
import os
89
from knack.log import get_logger
910

1011
from azure.cli.command_modules.storage.util import (create_blob_service_from_storage_client,
1112
create_file_share_from_storage_client,
1213
create_short_lived_share_sas,
1314
create_short_lived_container_sas,
1415
filter_none, collect_blobs, collect_files,
15-
mkdir_p, guess_content_type)
16+
mkdir_p, guess_content_type, normalize_blob_file_path)
1617
from azure.cli.command_modules.storage.url_quote_util import encode_for_url, make_encoded_file_url_and_params
1718

1819

@@ -43,8 +44,8 @@ def set_delete_policy(client, enable=None, days_retained=None):
4344
return client.get_blob_service_properties().delete_retention_policy
4445

4546

46-
def storage_blob_copy_batch(cmd, client, source_client,
47-
destination_container=None, source_container=None, source_share=None,
47+
def storage_blob_copy_batch(cmd, client, source_client, destination_container=None,
48+
destination_path=None, source_container=None, source_share=None,
4849
source_sas=None, pattern=None, dryrun=False):
4950
"""Copy a group of blob or files to a blob container."""
5051
logger = None
@@ -73,7 +74,7 @@ def action_blob_copy(blob_name):
7374
if dryrun:
7475
logger.warning(' - copy blob %s', blob_name)
7576
else:
76-
return _copy_blob_to_blob_container(client, source_client, destination_container,
77+
return _copy_blob_to_blob_container(client, source_client, destination_container, destination_path,
7778
source_container, source_sas, blob_name)
7879

7980
return list(filter_none(action_blob_copy(blob) for blob in collect_blobs(source_client,
@@ -94,10 +95,9 @@ def action_blob_copy(blob_name):
9495
def action_file_copy(file_info):
9596
dir_name, file_name = file_info
9697
if dryrun:
97-
import os.path
9898
logger.warning(' - copy file %s', os.path.join(dir_name, file_name))
9999
else:
100-
return _copy_file_to_blob_container(client, source_client, destination_container,
100+
return _copy_file_to_blob_container(client, source_client, destination_container, destination_path,
101101
source_share, source_sas, dir_name, file_name)
102102

103103
return list(filter_none(action_file_copy(file) for file in collect_files(cmd,
@@ -111,8 +111,8 @@ def action_file_copy(file_info):
111111
# pylint: disable=unused-argument
112112
def storage_blob_download_batch(client, source, destination, source_container_name, pattern=None, dryrun=False,
113113
progress_callback=None, max_connections=2):
114+
114115
def _download_blob(blob_service, container, destination_folder, blob_name):
115-
import os
116116
# TODO: try catch IO exception
117117
destination_path = os.path.join(destination_folder, blob_name)
118118
destination_folder = os.path.dirname(destination_path)
@@ -140,13 +140,14 @@ def _download_blob(blob_service, container, destination_folder, blob_name):
140140

141141

142142
def storage_blob_upload_batch(cmd, client, source, destination, pattern=None, # pylint: disable=too-many-locals
143-
source_files=None,
143+
source_files=None, destination_path=None,
144144
destination_container_name=None, blob_type=None,
145145
content_settings=None, metadata=None, validate_content=False,
146146
maxsize_condition=None, max_connections=2, lease_id=None, progress_callback=None,
147147
if_modified_since=None, if_unmodified_since=None, if_match=None,
148148
if_none_match=None, timeout=None, dryrun=False):
149149
def _create_return_result(blob_name, blob_content_settings, upload_result=None):
150+
blob_name = normalize_blob_file_path(destination_path, blob_name)
150151
return {
151152
'Blob': client.make_blob_url(destination_container_name, blob_name),
152153
'Type': blob_content_settings.content_type,
@@ -170,7 +171,8 @@ def _create_return_result(blob_name, blob_content_settings, upload_result=None):
170171
for src, dst in source_files or []:
171172
logger.warning('uploading %s', src)
172173
guessed_content_settings = guess_content_type(src, content_settings, t_content_settings)
173-
result = upload_blob(cmd, client, destination_container_name, dst, src,
174+
result = upload_blob(cmd, client, destination_container_name,
175+
normalize_blob_file_path(destination_path, dst), src,
174176
blob_type=blob_type, content_settings=guessed_content_settings, metadata=metadata,
175177
validate_content=validate_content, maxsize_condition=maxsize_condition,
176178
max_connections=max_connections, lease_id=lease_id,
@@ -220,7 +222,6 @@ def upload_append_blob():
220222

221223
def upload_block_blob():
222224
# increase the block size to 100MB when the file is larger than 200GB
223-
import os.path
224225
if os.path.isfile(file_path) and os.stat(file_path).st_size > 200 * 1024 * 1024 * 1024:
225226
client.MAX_BLOCK_SIZE = 100 * 1024 * 1024
226227
client.MAX_SINGLE_PUT_SIZE = 256 * 1024 * 1024
@@ -291,35 +292,34 @@ def _delete_blob(blob_name):
291292
return [_delete_blob(blob) for blob in source_blobs]
292293

293294

294-
def _copy_blob_to_blob_container(blob_service, source_blob_service, destination_container,
295+
def _copy_blob_to_blob_container(blob_service, source_blob_service, destination_container, destination_path,
295296
source_container, source_sas, source_blob_name):
296297
from azure.common import AzureException
297298
source_blob_url = source_blob_service.make_blob_url(source_container, encode_for_url(source_blob_name),
298299
sas_token=source_sas)
299-
300+
destination_blob_name = normalize_blob_file_path(destination_path, source_blob_name)
300301
try:
301-
blob_service.copy_blob(destination_container, source_blob_name, source_blob_url)
302-
return blob_service.make_blob_url(destination_container, source_blob_name)
302+
blob_service.copy_blob(destination_container, destination_blob_name, source_blob_url)
303+
return blob_service.make_blob_url(destination_container, destination_blob_name)
303304
except AzureException:
304305
from knack.util import CLIError
305306
error_template = 'Failed to copy blob {} to container {}.'
306307
raise CLIError(error_template.format(source_blob_name, destination_container))
307308

308309

309-
def _copy_file_to_blob_container(blob_service, source_file_service, destination_container,
310+
def _copy_file_to_blob_container(blob_service, source_file_service, destination_container, destination_path,
310311
source_share, source_sas, source_file_dir, source_file_name):
311312
from azure.common import AzureException
312-
import os.path
313313
file_url, source_file_dir, source_file_name = \
314314
make_encoded_file_url_and_params(source_file_service, source_share, source_file_dir,
315315
source_file_name, source_sas)
316316

317-
blob_name = os.path.join(source_file_dir, source_file_name) \
318-
if source_file_dir else source_file_name
317+
source_path = os.path.join(source_file_dir, source_file_name) if source_file_dir else source_file_name
318+
destination_blob_name = normalize_blob_file_path(destination_path, source_path)
319319

320320
try:
321-
blob_service.copy_blob(destination_container, blob_name=blob_name, copy_source=file_url)
322-
return blob_service.make_blob_url(destination_container, blob_name)
321+
blob_service.copy_blob(destination_container, destination_blob_name, file_url)
322+
return blob_service.make_blob_url(destination_container, destination_blob_name)
323323
except AzureException as ex:
324324
from knack.util import CLIError
325325
error_template = 'Failed to copy file {} to container {}. {}'

src/command_modules/azure-cli-storage/azure/cli/command_modules/storage/operations/file.py

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
Commands for storage file share operations
88
"""
99

10+
import os
1011
from knack.log import get_logger
1112

1213
from azure.cli.command_modules.storage.util import (filter_none, collect_blobs, collect_files,
@@ -30,12 +31,12 @@ def list_share_files(cmd, client, share_name, directory_name=None, timeout=None,
3031
return generator
3132

3233

33-
def storage_file_upload_batch(cmd, client, destination, source, pattern=None, dryrun=False, validate_content=False,
34-
content_settings=None, max_connections=1, metadata=None, progress_callback=None):
34+
def storage_file_upload_batch(cmd, client, destination, source, destination_path=None, pattern=None, dryrun=False,
35+
validate_content=False, content_settings=None, max_connections=1, metadata=None,
36+
progress_callback=None):
3537
""" Upload local files to Azure Storage File Share in batch """
3638

37-
from ..util import glob_files_locally
38-
import os.path
39+
from azure.cli.command_modules.storage.util import glob_files_locally, normalize_blob_file_path
3940

4041
source_files = [c for c in glob_files_locally(source, pattern)]
4142
logger = get_logger(__name__)
@@ -46,13 +47,14 @@ def storage_file_upload_batch(cmd, client, destination, source, pattern=None, dr
4647
logger.info(' account %s', client.account_name)
4748
logger.info(' share %s', destination)
4849
logger.info(' total %d', len(source_files))
49-
return [{'File': client.make_file_url(destination, os.path.dirname(src), os.path.basename(dst)),
50+
return [{'File': client.make_file_url(destination, os.path.dirname(dst) or None, os.path.basename(dst)),
5051
'Type': guess_content_type(src, content_settings, settings_class).content_type} for src, dst in
5152
source_files]
5253

5354
# TODO: Performance improvement
5455
# 1. Upload files in parallel
5556
def _upload_action(src, dst):
57+
dst = normalize_blob_file_path(destination_path, dst)
5658
dir_name = os.path.dirname(dst)
5759
file_name = os.path.basename(dst)
5860

@@ -79,8 +81,7 @@ def storage_file_download_batch(cmd, client, source, destination, pattern=None,
7981
Download files from file share to local directory in batch
8082
"""
8183

82-
from ..util import glob_files_remotely, mkdir_p
83-
import os.path
84+
from azure.cli.command_modules.storage.util import glob_files_remotely, mkdir_p
8485

8586
source_files = glob_files_remotely(cmd, client, source, pattern)
8687

@@ -181,7 +182,6 @@ def action_blob_copy(blob_name):
181182
def action_file_copy(file_info):
182183
dir_name, file_name = file_info
183184
if dryrun:
184-
import os.path
185185
logger.warning(' - copy file %s', os.path.join(dir_name, file_name))
186186
else:
187187
return _create_file_and_directory_from_file(client, source_client, destination_share, source_share,
@@ -207,7 +207,7 @@ def delete_action(file_pair):
207207

208208
return client.delete_file(**delete_file_args)
209209

210-
from ..util import glob_files_remotely
210+
from azure.cli.command_modules.storage.util import glob_files_remotely
211211
source_files = list(glob_files_remotely(cmd, client, source, pattern))
212212

213213
if dryrun:
@@ -230,10 +230,10 @@ def _create_file_and_directory_from_blob(file_service, blob_service, share, cont
230230
Copy a blob to file share and create the directory if needed.
231231
"""
232232
from azure.common import AzureException
233-
import os.path
233+
from azure.cli.command_modules.storage.util import normalize_blob_file_path
234234

235235
blob_url = blob_service.make_blob_url(container, encode_for_url(blob_name), sas_token=sas)
236-
full_path = os.path.join(destination_dir, blob_name) if destination_dir else blob_name
236+
full_path = normalize_blob_file_path(destination_dir, blob_name)
237237
file_name = os.path.basename(full_path)
238238
dir_name = os.path.dirname(full_path)
239239
_make_directory_in_files_share(file_service, share, dir_name, existing_dirs)
@@ -255,14 +255,13 @@ def _create_file_and_directory_from_file(file_service, source_file_service, shar
255255
Copy a file from one file share to another
256256
"""
257257
from azure.common import AzureException
258-
import os.path
258+
from azure.cli.command_modules.storage.util import normalize_blob_file_path
259259

260260
file_url, source_file_dir, source_file_name = make_encoded_file_url_and_params(source_file_service, source_share,
261261
source_file_dir, source_file_name,
262262
sas_token=sas)
263263

264-
full_path = os.path.join(destination_dir, source_file_dir, source_file_name) if destination_dir else os.path.join(
265-
source_file_dir, source_file_name)
264+
full_path = normalize_blob_file_path(destination_dir, os.path.join(source_file_dir, source_file_name))
266265
file_name = os.path.basename(full_path)
267266
dir_name = os.path.dirname(full_path)
268267
_make_directory_in_files_share(file_service, share, dir_name, existing_dirs)
@@ -286,7 +285,6 @@ def _make_directory_in_files_share(file_service, file_share, directory_path, exi
286285
which already exists.
287286
"""
288287
from azure.common import AzureHttpError
289-
import os.path
290288

291289
if not directory_path:
292290
return

0 commit comments

Comments
 (0)