зеркало из https://github.com/Azure/blobxfer.git
1000 строки
30 KiB
Python
1000 строки
30 KiB
Python
# Copyright (c) Microsoft Corporation
|
|
#
|
|
# All rights reserved.
|
|
#
|
|
# MIT License
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person obtaining a
|
|
# copy of this software and associated documentation files (the "Software"),
|
|
# to deal in the Software without restriction, including without limitation
|
|
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
# and/or sell copies of the Software, and to permit persons to whom the
|
|
# Software is furnished to do so, subject to the following conditions:
|
|
#
|
|
# The above copyright notice and this permission notice shall be included in
|
|
# all copies or substantial portions of the Software.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
# DEALINGS IN THE SOFTWARE.
|
|
|
|
# compat imports
|
|
from __future__ import absolute_import, division, print_function
|
|
from builtins import ( # noqa
|
|
bytes, dict, int, list, object, range, ascii, chr, hex, input,
|
|
next, oct, open, pow, round, super, filter, map, zip)
|
|
# stdlib imports
|
|
import json
|
|
import logging
|
|
try:
|
|
import pathlib2 as pathlib
|
|
except ImportError: # noqa
|
|
import pathlib
|
|
# non-stdlib imports
|
|
import click
|
|
import ruamel.yaml
|
|
# blobxfer library imports
|
|
import blobxfer.api
|
|
import blobxfer.util
|
|
# local imports
|
|
try:
|
|
import cli.settings as settings
|
|
except (SystemError, ImportError): # noqa
|
|
try:
|
|
from . import settings
|
|
except (SystemError, ImportError): # noqa
|
|
# for local testing
|
|
import settings
|
|
|
|
# create logger
|
|
logger = logging.getLogger('blobxfer')
|
|
# global defines
|
|
_CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])
|
|
|
|
|
|
class CliContext(object):
|
|
"""CliContext class: holds context for CLI commands"""
|
|
def __init__(self):
|
|
"""Ctor for CliContext"""
|
|
self.config = None
|
|
self.cli_options = {}
|
|
self.credentials = None
|
|
self.general_options = None
|
|
self.show_config = False
|
|
|
|
def initialize(self, action):
|
|
# type: (CliContext, settings.TransferAction) -> None
|
|
"""Initialize context
|
|
:param CliContext self: this
|
|
:param settings.TransferAction action: transfer action
|
|
"""
|
|
self._init_config()
|
|
self.general_options = settings.create_general_options(
|
|
self.config, action)
|
|
self.credentials = settings.create_azure_storage_credentials(
|
|
self.config, self.general_options)
|
|
|
|
def _read_yaml_file(self, yaml_file):
|
|
# type: (CliContext, pathlib.Path) -> None
|
|
"""Read a yaml file into self.config
|
|
:param CliContext self: this
|
|
:param pathlib.Path yaml_file: yaml file to load
|
|
"""
|
|
with yaml_file.open('r') as f:
|
|
if self.config is None:
|
|
self.config = ruamel.yaml.load(
|
|
f, Loader=ruamel.yaml.RoundTripLoader)
|
|
else:
|
|
self.config = blobxfer.util.merge_dict(
|
|
ruamel.yaml.load(f, Loader=ruamel.yaml.RoundTripLoader),
|
|
self.config)
|
|
|
|
def _init_config(self):
|
|
# type: (CliContext) -> None
|
|
"""Initializes configuration of the context
|
|
:param CliContext self: this
|
|
"""
|
|
# load yaml config file into memory
|
|
if blobxfer.util.is_not_empty(self.cli_options['yaml_config']):
|
|
yaml_config = pathlib.Path(self.cli_options['yaml_config'])
|
|
self._read_yaml_file(yaml_config)
|
|
if self.config is None:
|
|
self.config = {}
|
|
# merge "global" cli options with config
|
|
settings.merge_global_settings(self.config, self.cli_options)
|
|
# set log file if specified
|
|
logfile = self.config['options'].get('log_file', None)
|
|
blobxfer.util.setup_logger(logger, logfile)
|
|
# set verbose logging
|
|
if self.config['options'].get('verbose', False):
|
|
blobxfer.util.set_verbose_logger_handlers()
|
|
# set azure storage logging level
|
|
azstorage_logger = logging.getLogger('azure.storage')
|
|
if self.config['options'].get('enable_azure_storage_logger', False):
|
|
blobxfer.util.setup_logger(azstorage_logger, logfile)
|
|
azstorage_logger.setLevel(logging.INFO)
|
|
else:
|
|
# disable azure storage logging: setting logger level to CRITICAL
|
|
# effectively disables logging from azure storage
|
|
azstorage_logger.setLevel(logging.CRITICAL)
|
|
# output mixed config
|
|
if self.show_config:
|
|
logger.debug('config: \n{}'.format(
|
|
json.dumps(self.config, indent=4)))
|
|
logger.debug('cli config: \n{}'.format(
|
|
json.dumps(
|
|
self.cli_options[self.cli_options['_action']],
|
|
indent=4, sort_keys=True)))
|
|
del self.show_config
|
|
|
|
|
|
# create a pass decorator for shared context between commands
|
|
pass_cli_context = click.make_pass_decorator(CliContext, ensure=True)
|
|
|
|
|
|
def _config_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['yaml_config'] = value
|
|
return value
|
|
return click.option(
|
|
'--config',
|
|
expose_value=False,
|
|
default=None,
|
|
help='YAML configuration file',
|
|
envvar='BLOBXFER_CONFIG_FILE',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _crypto_processes_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['crypto_processes'] = value
|
|
return value
|
|
return click.option(
|
|
'--crypto-processes',
|
|
expose_value=False,
|
|
type=int,
|
|
default=None,
|
|
help='Concurrent crypto processes (download only)',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _disk_threads_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['disk_threads'] = value
|
|
return value
|
|
return click.option(
|
|
'--disk-threads',
|
|
expose_value=False,
|
|
type=int,
|
|
default=None,
|
|
help='Concurrent disk threads',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _enable_azure_storage_logger_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['enable_azure_storage_logger'] = value
|
|
return value
|
|
return click.option(
|
|
'--enable-azure-storage-logger',
|
|
expose_value=False,
|
|
is_flag=True,
|
|
default=False,
|
|
help='Enable Azure Storage logger output [False]',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _log_file_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['log_file'] = value
|
|
return value
|
|
return click.option(
|
|
'--log-file',
|
|
expose_value=False,
|
|
default=None,
|
|
help='Log to file specified; this must be specified for progress '
|
|
'bar to show',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _max_retries_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['max_retries'] = value
|
|
return value
|
|
return click.option(
|
|
'--max-retries',
|
|
expose_value=False,
|
|
type=int,
|
|
default=None,
|
|
help='Maximum number of retries for a request; negative values are '
|
|
'unlimited [10]',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _md5_processes_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['md5_processes'] = value
|
|
return value
|
|
return click.option(
|
|
'--md5-processes',
|
|
expose_value=False,
|
|
type=int,
|
|
default=None,
|
|
help='Concurrent MD5 processes',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _progress_bar_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['progress_bar'] = value
|
|
return value
|
|
return click.option(
|
|
'--progress-bar/--no-progress-bar',
|
|
expose_value=False,
|
|
default=None,
|
|
help='Display progress bar instead of console logs; log file must '
|
|
'be specified [True]',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _proxy_host_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['proxy_host'] = value
|
|
return value
|
|
return click.option(
|
|
'--proxy-host',
|
|
expose_value=False,
|
|
default=None,
|
|
help='Proxy host in the format of IP:Port',
|
|
envvar='BLOBXFER_PROXY_HOST',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _proxy_password_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['proxy_password'] = value
|
|
return value
|
|
return click.option(
|
|
'--proxy-password',
|
|
expose_value=False,
|
|
default=None,
|
|
help='Proxy password',
|
|
envvar='BLOBXFER_PROXY_PASSWORD',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _proxy_username_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['proxy_username'] = value
|
|
return value
|
|
return click.option(
|
|
'--proxy-username',
|
|
expose_value=False,
|
|
default=None,
|
|
help='Proxy username',
|
|
envvar='BLOBXFER_PROXY_USERNAME',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _resume_file_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['resume_file'] = value
|
|
return value
|
|
return click.option(
|
|
'--resume-file',
|
|
expose_value=False,
|
|
default=None,
|
|
help='Save or use resume file specified',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _show_config_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.show_config = value
|
|
return value
|
|
return click.option(
|
|
'--show-config',
|
|
expose_value=False,
|
|
is_flag=True,
|
|
help='Show configuration',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _timeout_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['timeout'] = value
|
|
return value
|
|
return click.option(
|
|
'--timeout',
|
|
expose_value=False,
|
|
type=float,
|
|
default=None,
|
|
help='Timeout, in seconds, applied to both connect and read '
|
|
'operations',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _transfer_threads_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['transfer_threads'] = value
|
|
return value
|
|
return click.option(
|
|
'--transfer-threads',
|
|
expose_value=False,
|
|
type=int,
|
|
default=None,
|
|
help='Concurrent transfer threads',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _verbose_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['verbose'] = value
|
|
return value
|
|
return click.option(
|
|
'-v', '--verbose',
|
|
expose_value=False,
|
|
is_flag=True,
|
|
default=None,
|
|
help='Verbose output',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _quiet_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['quiet'] = value
|
|
return value
|
|
return click.option(
|
|
'-q', '--quiet',
|
|
expose_value=False,
|
|
is_flag=True,
|
|
default=None,
|
|
help='Quiet mode',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _local_resource_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['local_resource'] = value
|
|
return value
|
|
return click.option(
|
|
'--local-path',
|
|
expose_value=False,
|
|
default=None,
|
|
help='Local path; use - for stdin',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _storage_account_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['storage_account'] = value
|
|
return value
|
|
return click.option(
|
|
'--storage-account',
|
|
expose_value=False,
|
|
default=None,
|
|
help='Storage account name',
|
|
envvar='BLOBXFER_STORAGE_ACCOUNT',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _remote_path_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['remote_path'] = value
|
|
return value
|
|
return click.option(
|
|
'--remote-path',
|
|
expose_value=False,
|
|
default=None,
|
|
help='Remote path on Azure Storage',
|
|
callback=callback)(f)
|
|
|
|
|
|
def common_options(f):
|
|
f = _verbose_option(f)
|
|
f = _transfer_threads_option(f)
|
|
f = _timeout_option(f)
|
|
f = _show_config_option(f)
|
|
f = _resume_file_option(f)
|
|
f = _quiet_option(f)
|
|
f = _proxy_username_option(f)
|
|
f = _proxy_password_option(f)
|
|
f = _proxy_host_option(f)
|
|
f = _progress_bar_option(f)
|
|
f = _md5_processes_option(f)
|
|
f = _max_retries_option(f)
|
|
f = _log_file_option(f)
|
|
f = _enable_azure_storage_logger_option(f)
|
|
f = _disk_threads_option(f)
|
|
f = _crypto_processes_option(f)
|
|
f = _config_option(f)
|
|
return f
|
|
|
|
|
|
def upload_download_options(f):
|
|
f = _remote_path_option(f)
|
|
f = _storage_account_option(f)
|
|
f = _local_resource_option(f)
|
|
return f
|
|
|
|
|
|
def _access_key_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['access_key'] = value
|
|
return value
|
|
return click.option(
|
|
'--storage-account-key',
|
|
expose_value=False,
|
|
default=None,
|
|
help='Storage account access key',
|
|
envvar='BLOBXFER_STORAGE_ACCOUNT_KEY',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _access_tier_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['access_tier'] = value
|
|
return value
|
|
return click.option(
|
|
'--access-tier',
|
|
expose_value=False,
|
|
default=None,
|
|
help='Access tier to apply to target (block blob only)',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _chunk_size_bytes_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['chunk_size_bytes'] = value
|
|
return value
|
|
return click.option(
|
|
'--chunk-size-bytes',
|
|
expose_value=False,
|
|
type=int,
|
|
default=None,
|
|
help='Block or chunk size in bytes; set to 0 for auto-select '
|
|
'on upload [0]',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _delete_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['delete'] = value
|
|
return value
|
|
return click.option(
|
|
'--delete',
|
|
expose_value=False,
|
|
is_flag=True,
|
|
default=None,
|
|
help='Delete extraneous files on target [False]',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _distribution_mode(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['distribution_mode'] = value
|
|
return value
|
|
return click.option(
|
|
'--distribution-mode',
|
|
expose_value=False,
|
|
default=None,
|
|
help='Vectored IO distribution mode: disabled, replica, '
|
|
'stripe [disabled]',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _endpoint_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['endpoint'] = value
|
|
return value
|
|
return click.option(
|
|
'--endpoint',
|
|
expose_value=False,
|
|
default=None,
|
|
help='Azure Storage endpoint [core.windows.net]',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _exclude_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['exclude'] = value
|
|
return value
|
|
return click.option(
|
|
'--exclude',
|
|
expose_value=False,
|
|
multiple=True,
|
|
default=None,
|
|
help='Exclude pattern',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _file_attributes(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['file_attributes'] = value
|
|
return value
|
|
return click.option(
|
|
'--file-attributes/--no-file-attributes',
|
|
expose_value=False,
|
|
default=None,
|
|
help='Store or restore file attributes [False]',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _file_md5_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['file_md5'] = value
|
|
return value
|
|
return click.option(
|
|
'--file-md5/--no-file-md5',
|
|
expose_value=False,
|
|
default=None,
|
|
help='Compute file MD5 [False]',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _include_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['include'] = value
|
|
return value
|
|
return click.option(
|
|
'--include',
|
|
expose_value=False,
|
|
multiple=True,
|
|
default=None,
|
|
help='Include pattern',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _mode_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['mode'] = value
|
|
return value
|
|
return click.option(
|
|
'--mode',
|
|
expose_value=False,
|
|
default=None,
|
|
help='Transfer mode: auto, append, block, file, page [auto]',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _one_shot_bytes_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['one_shot_bytes'] = value
|
|
return value
|
|
return click.option(
|
|
'--one-shot-bytes',
|
|
expose_value=False,
|
|
type=int,
|
|
default=None,
|
|
help='File sizes less than or equal to the specified byte threshold '
|
|
'will be uploaded as one-shot for block blobs; the valid range that '
|
|
'can be specified is 0 to 256MiB [0]',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _overwrite_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['overwrite'] = value
|
|
return value
|
|
return click.option(
|
|
'--overwrite/--no-overwrite',
|
|
expose_value=False,
|
|
default=None,
|
|
help='Overwrite destination if exists. For append blobs, '
|
|
'--no-overwrite will append to any existing blob. [True]',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _recursive_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['recursive'] = value
|
|
return value
|
|
return click.option(
|
|
'--recursive/--no-recursive',
|
|
expose_value=False,
|
|
default=None,
|
|
help='Recursive [True]',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _rename_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['rename'] = value
|
|
return value
|
|
return click.option(
|
|
'--rename',
|
|
expose_value=False,
|
|
is_flag=True,
|
|
default=None,
|
|
help='Rename to specified destination for a single object. '
|
|
'Automatically enabled with stdin source. [False]',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _rsa_private_key_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['rsa_private_key'] = value
|
|
return value
|
|
return click.option(
|
|
'--rsa-private-key',
|
|
expose_value=False,
|
|
default=None,
|
|
help='RSA private key PEM file',
|
|
envvar='BLOBXFER_RSA_PRIVATE_KEY',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _rsa_private_key_passphrase_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['rsa_private_key_passphrase'] = value
|
|
return value
|
|
return click.option(
|
|
'--rsa-private-key-passphrase',
|
|
expose_value=False,
|
|
default=None,
|
|
help='RSA private key passphrase',
|
|
envvar='BLOBXFER_RSA_PRIVATE_KEY_PASSPHRASE',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _rsa_public_key_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['rsa_public_key'] = value
|
|
return value
|
|
return click.option(
|
|
'--rsa-public-key',
|
|
expose_value=False,
|
|
default=None,
|
|
help='RSA public key PEM file',
|
|
envvar='BLOBXFER_RSA_PUBLIC_KEY',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _sas_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['sas'] = value
|
|
return value
|
|
return click.option(
|
|
'--sas',
|
|
expose_value=False,
|
|
default=None,
|
|
help='Shared access signature',
|
|
envvar='BLOBXFER_SAS',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _skip_on_filesize_match_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['skip_on_filesize_match'] = value
|
|
return value
|
|
return click.option(
|
|
'--skip-on-filesize-match',
|
|
expose_value=False,
|
|
is_flag=True,
|
|
default=None,
|
|
help='Skip on equivalent file size [False]',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _skip_on_lmt_ge_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['skip_on_lmt_ge'] = value
|
|
return value
|
|
return click.option(
|
|
'--skip-on-lmt-ge',
|
|
expose_value=False,
|
|
is_flag=True,
|
|
default=None,
|
|
help='Skip on last modified time greater than or equal to [False]',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _skip_on_md5_match_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['skip_on_md5_match'] = value
|
|
return value
|
|
return click.option(
|
|
'--skip-on-md5-match',
|
|
expose_value=False,
|
|
is_flag=True,
|
|
default=None,
|
|
help='Skip on MD5 match [False]',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _stdin_as_page_blob_size_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['stdin_as_page_blob_size'] = value
|
|
return value
|
|
return click.option(
|
|
'--stdin-as-page-blob-size',
|
|
expose_value=False,
|
|
type=int,
|
|
default=None,
|
|
help='Size of page blob with input from stdin [0]',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _strip_components_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['strip_components'] = value
|
|
return value
|
|
return click.option(
|
|
'--strip-components',
|
|
expose_value=False,
|
|
type=int,
|
|
default=None,
|
|
help='Strip leading file path components: local path for upload '
|
|
'or remote path for download [0]',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _stripe_chunk_size_bytes_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['stripe_chunk_size_bytes'] = value
|
|
return value
|
|
return click.option(
|
|
'--stripe-chunk-size-bytes',
|
|
expose_value=False,
|
|
type=int,
|
|
default=None,
|
|
help='Vectored IO stripe width in bytes [1073741824]',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _sync_copy_dest_access_key_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['sync_copy_dest_access_key'] = value
|
|
return value
|
|
return click.option(
|
|
'--sync-copy-dest-storage-account-key',
|
|
expose_value=False,
|
|
default=None,
|
|
help='Storage account access key for synccopy destination',
|
|
envvar='BLOBXFER_SYNC_COPY_DEST_STORAGE_ACCOUNT_KEY',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _sync_copy_dest_mode_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['sync_copy_dest_mode'] = value
|
|
return value
|
|
return click.option(
|
|
'--sync-copy-dest-mode',
|
|
expose_value=False,
|
|
default=None,
|
|
help='Mode for synccopy destination',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _sync_copy_dest_remote_path_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['sync_copy_dest_remote_path'] = value
|
|
return value
|
|
return click.option(
|
|
'--sync-copy-dest-remote-path',
|
|
expose_value=False,
|
|
default=None,
|
|
help='Remote path on Azure Storage for synccopy destination',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _sync_copy_dest_sas_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['sync_copy_dest_sas'] = value
|
|
return value
|
|
return click.option(
|
|
'--sync-copy-dest-sas',
|
|
expose_value=False,
|
|
default=None,
|
|
help='Shared access signature for synccopy destination',
|
|
envvar='BLOBXFER_SYNC_COPY_DEST_SAS',
|
|
callback=callback)(f)
|
|
|
|
|
|
def _sync_copy_dest_storage_account_option(f):
|
|
def callback(ctx, param, value):
|
|
clictx = ctx.ensure_object(CliContext)
|
|
clictx.cli_options['sync_copy_dest_storage_account'] = value
|
|
return value
|
|
return click.option(
|
|
'--sync-copy-dest-storage-account',
|
|
expose_value=False,
|
|
default=None,
|
|
help='Storage account name for synccopy destination',
|
|
envvar='BLOBXFER_SYNC_COPY_DEST_STORAGE_ACCOUNT',
|
|
callback=callback)(f)
|
|
|
|
|
|
def upload_options(f):
|
|
f = _stripe_chunk_size_bytes_option(f)
|
|
f = _strip_components_option(f)
|
|
f = _stdin_as_page_blob_size_option(f)
|
|
f = _skip_on_md5_match_option(f)
|
|
f = _skip_on_lmt_ge_option(f)
|
|
f = _skip_on_filesize_match_option(f)
|
|
f = _sas_option(f)
|
|
f = _rsa_public_key_option(f)
|
|
f = _rsa_private_key_passphrase_option(f)
|
|
f = _rsa_private_key_option(f)
|
|
f = _rename_option(f)
|
|
f = _recursive_option(f)
|
|
f = _overwrite_option(f)
|
|
f = _one_shot_bytes_option(f)
|
|
f = _mode_option(f)
|
|
f = _include_option(f)
|
|
f = _file_md5_option(f)
|
|
f = _file_attributes(f)
|
|
f = _exclude_option(f)
|
|
f = _endpoint_option(f)
|
|
f = _distribution_mode(f)
|
|
f = _delete_option(f)
|
|
f = _chunk_size_bytes_option(f)
|
|
f = _access_tier_option(f)
|
|
f = _access_key_option(f)
|
|
return f
|
|
|
|
|
|
def download_options(f):
|
|
f = _strip_components_option(f)
|
|
f = _skip_on_md5_match_option(f)
|
|
f = _skip_on_lmt_ge_option(f)
|
|
f = _skip_on_filesize_match_option(f)
|
|
f = _sas_option(f)
|
|
f = _rsa_private_key_passphrase_option(f)
|
|
f = _rsa_private_key_option(f)
|
|
f = _rename_option(f)
|
|
f = _recursive_option(f)
|
|
f = _overwrite_option(f)
|
|
f = _mode_option(f)
|
|
f = _include_option(f)
|
|
f = _file_md5_option(f)
|
|
f = _file_attributes(f)
|
|
f = _exclude_option(f)
|
|
f = _endpoint_option(f)
|
|
f = _delete_option(f)
|
|
f = _chunk_size_bytes_option(f)
|
|
f = _access_key_option(f)
|
|
return f
|
|
|
|
|
|
def sync_copy_options(f):
|
|
f = _sync_copy_dest_storage_account_option(f)
|
|
f = _sync_copy_dest_sas_option(f)
|
|
f = _sync_copy_dest_remote_path_option(f)
|
|
f = _sync_copy_dest_mode_option(f)
|
|
f = _sync_copy_dest_access_key_option(f)
|
|
f = _storage_account_option(f)
|
|
f = _skip_on_md5_match_option(f)
|
|
f = _skip_on_lmt_ge_option(f)
|
|
f = _skip_on_filesize_match_option(f)
|
|
f = _sas_option(f)
|
|
f = _remote_path_option(f)
|
|
f = _overwrite_option(f)
|
|
f = _mode_option(f)
|
|
f = _include_option(f)
|
|
f = _exclude_option(f)
|
|
f = _endpoint_option(f)
|
|
f = _chunk_size_bytes_option(f)
|
|
f = _access_tier_option(f)
|
|
f = _access_key_option(f)
|
|
return f
|
|
|
|
|
|
@click.group(context_settings=_CONTEXT_SETTINGS)
|
|
@click.version_option(version=blobxfer.__version__)
|
|
@click.pass_context
|
|
def cli(ctx):
|
|
"""blobxfer: Azure Storage transfer tool"""
|
|
pass
|
|
|
|
|
|
@cli.command('download')
|
|
@upload_download_options
|
|
@download_options
|
|
@common_options
|
|
@pass_cli_context
|
|
def download(ctx):
|
|
"""Download blobs or files from Azure Storage"""
|
|
settings.add_cli_options(ctx.cli_options, settings.TransferAction.Download)
|
|
ctx.initialize(settings.TransferAction.Download)
|
|
specs = settings.create_download_specifications(
|
|
ctx.cli_options, ctx.config)
|
|
del ctx.cli_options
|
|
for spec in specs:
|
|
blobxfer.api.Downloader(
|
|
ctx.general_options, ctx.credentials, spec
|
|
).start()
|
|
|
|
|
|
@cli.command('synccopy')
|
|
@sync_copy_options
|
|
@common_options
|
|
@pass_cli_context
|
|
def synccopy(ctx):
|
|
"""Synchronously copy blobs or files between Azure Storage accounts"""
|
|
settings.add_cli_options(ctx.cli_options, settings.TransferAction.Synccopy)
|
|
ctx.initialize(settings.TransferAction.Synccopy)
|
|
specs = settings.create_synccopy_specifications(
|
|
ctx.cli_options, ctx.config)
|
|
del ctx.cli_options
|
|
for spec in specs:
|
|
blobxfer.api.SyncCopy(
|
|
ctx.general_options, ctx.credentials, spec
|
|
).start()
|
|
|
|
|
|
@cli.command('upload')
|
|
@upload_download_options
|
|
@upload_options
|
|
@common_options
|
|
@pass_cli_context
|
|
def upload(ctx):
|
|
"""Upload files to Azure Storage"""
|
|
settings.add_cli_options(ctx.cli_options, settings.TransferAction.Upload)
|
|
ctx.initialize(settings.TransferAction.Upload)
|
|
specs = settings.create_upload_specifications(
|
|
ctx.cli_options, ctx.config)
|
|
del ctx.cli_options
|
|
for spec in specs:
|
|
blobxfer.api.Uploader(
|
|
ctx.general_options, ctx.credentials, spec
|
|
).start()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
cli()
|