Skip to content
This repository was archived by the owner on Jul 18, 2024. It is now read-only.

Commit 4b28067

Browse files
authored
Merge pull request #7 from Azure/develop
Support account-level SAS keys
2 parents 3a21884 + 14b6524 commit 4b28067

File tree

5 files changed

+100
-21
lines changed

5 files changed

+100
-21
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ install:
2626
- travis_retry pip install coveralls flake8 mock pytest pytest-cov requests_mock
2727
script:
2828
- flake8 blobxfer.py test/test_blobxfer.py
29-
- PYTHONPATH=. py.test -l --cov-config .coveragerc --cov-report term-missing --cov blobxfer test/test_blobxfer.py
29+
- PYTHONPATH=. py.test -l --full-trace --cov-config .coveragerc --cov-report term-missing --cov blobxfer test/test_blobxfer.py
3030
after_success:
3131
- coveralls --rcfile=.coveragerc --verbose
3232

CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
## [Unreleased]
44

5+
## [0.12.0] - 2016-10-17
6+
#### Added
7+
- Support for Account-level SAS keys
8+
- Update README regarding non-normalized exceptions being thrown (#5)
9+
510
## [0.11.5] - 2016-10-03
611
#### Changed
712
- Update all dependencies to latest versions
@@ -168,7 +173,8 @@
168173
`--no-skiponmatch`.
169174
- 0.8.2: performance regression fixes
170175

171-
[Unreleased]: https://github.com/Azure/blobxfer/compare/0.11.5...HEAD
176+
[Unreleased]: https://github.com/Azure/blobxfer/compare/0.12.0...HEAD
177+
[0.12.0]: https://github.com/Azure/blobxfer/compare/0.11.5...0.12.0
172178
[0.11.5]: https://github.com/Azure/blobxfer/compare/0.11.4...0.11.5
173179
[0.11.4]: https://github.com/Azure/blobxfer/compare/v0.11.2...0.11.4
174180
[0.11.2]: https://github.com/Azure/blobxfer/compare/e5e435a...v0.11.2

README.rst

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ Installation
2626
pip install blobxfer
2727

2828
blobxfer is compatible with Python 2.7 and 3.3+. To install for Python 3, some
29-
distributions may use ``pip3`` instead.
29+
distributions may use ``pip3`` instead. If you do not want to install blobxfer
30+
as a system-wide binary and modify system-wide python packages, use the
31+
``--user`` flag with ``pip`` or ``pip3``.
3032

3133
blobxfer is also on `Docker Hub`_, and the Docker image for Linux can be
3234
pulled with the following command:
@@ -42,7 +44,7 @@ If you encounter difficulties installing the script, it may be due to the
4244
binary wheels provided by these dependencies (e.g., on Windows) or is able to
4345
compile the dependencies (i.e., ensure you have a C compiler, python, ssl,
4446
and ffi development libraries/headers installed prior to invoking pip). For
45-
instance, to install blobxfer on a fresh Ubuntu 14.04 installation for
47+
instance, to install blobxfer on a fresh Ubuntu 14.04/16.04 installation for
4648
Python 2.7, issue the following commands:
4749

4850
::
@@ -51,9 +53,6 @@ Python 2.7, issue the following commands:
5153
apt-get install -y build-essential libssl-dev libffi-dev libpython-dev python-dev python-pip
5254
pip install --upgrade blobxfer
5355

54-
If you do not want to modify system-wide dependencies, use the ``--user`` flag
55-
with ``pip``.
56-
5756
If you need more fine-grained control on installing dependencies, continue
5857
reading this section. Depending upon the desired mode of authentication with
5958
Azure and options, the script will require the following packages, some of
@@ -116,8 +115,10 @@ It is generally recommended to use SAS keys wherever appropriate; only HTTPS
116115
transport is used in the script. Please note that when using SAS keys that
117116
only container- or fileshare-level SAS keys will allow for entire directory
118117
uploading or container/fileshare downloading. The container/fileshare must
119-
also have been created beforehand, as containers/fileshares cannot be created
120-
using SAS keys.
118+
also have been created beforehand if using a service SAS, as
119+
containers/fileshares cannot be created using service SAS keys. Account-level
120+
SAS keys with a signed resource type of ``c`` or container will allow
121+
containers/fileshares to be created with SAS keys.
121122

122123
Example Usage
123124
-------------
@@ -274,6 +275,14 @@ path components as desired.
274275
General Notes
275276
-------------
276277

278+
- If the pyOpenSSL package is present, urllib3/requests may use this package
279+
(as discussed in the Performance Notes below), which may result in
280+
exceptions being thrown that are not normalized by urllib3. This may
281+
result in exceptions that should be retried, but are not. It is recommended
282+
to upgrade your Python where pyOpenSSL is not required for fully validating
283+
peers and such that blobxfer can operate without pyOpenSSL in a secure
284+
fashion. You can also run blobxfer via Docker or in a virtualenv
285+
environment without pyOpenSSL.
277286
- blobxfer does not take any leases on blobs or containers. It is up to
278287
the user to ensure that blobs are not modified while download/uploads
279288
are being performed.
@@ -282,11 +291,12 @@ General Notes
282291
- blobxfer will attempt to download from blob storage as-is. If the source
283292
filename is incompatible with the destination operating system, then
284293
failure may result.
285-
- When using SAS, the SAS key must be a container-level SAS if performing
286-
recursive directory upload or container download.
287-
- If uploading via SAS, the container must already be created in blob
288-
storage prior to upload. This is a limitation of SAS keys. The script
289-
will force disable container creation if a SAS key is specified.
294+
- When using SAS, the SAS key must be a container- or share-level SAS if
295+
performing recursive directory upload or container/file share download.
296+
- If uploading via service-level SAS keys, the container or file share must
297+
already be created in Azure storage prior to upload. Account-level SAS keys
298+
with the signed resource type of ``c`` or container-level permission will
299+
allow conatiner or file share creation.
290300
- For non-SAS requests, timeouts may not be properly honored due to
291301
limitations of the Azure Python SDK.
292302
- By default, files with matching MD5 checksums will be skipped for both
@@ -295,8 +305,8 @@ General Notes
295305
- When uploading files as page blobs, the content is page boundary
296306
byte-aligned. The MD5 for the blob is computed using the final aligned
297307
data if the source is not page boundary byte-aligned. This enables these
298-
page blobs or files to be skipped during subsequent download or upload,
299-
if the ``--no-skiponmatch`` parameter is not specified.
308+
page blobs or files to be skipped during subsequent download or upload by
309+
default (i.e., ``--no-skiponmatch`` parameter is not specified).
300310
- If ``--delete`` is specified, any remote files found that have no
301311
corresponding local file in directory upload mode will be deleted. Deletion
302312
occurs prior to any transfers, analogous to the delete-before rsync option.
@@ -366,7 +376,7 @@ Encryption Notes
366376
metadata. These metadata entries are used on download to configure the proper
367377
download and parameters for the decryption process as well as to authenticate
368378
the encryption. Encryption metadata set by blobxfer (or the Azure Storage
369-
.NET/Java client library) should not be modified or blobs may be
379+
.NET/Java client library) should not be modified or blobs/files may be
370380
unrecoverable.
371381
- Local files can be encrypted by blobxfer and stored in Azure Files and,
372382
correspondingly, remote files on Azure File shares can be decrypted by

blobxfer.py

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@
104104
# pylint: enable=W0622,C0103
105105

106106
# global defines
107-
_SCRIPT_VERSION = '0.11.5'
107+
_SCRIPT_VERSION = '0.12.0'
108108
_PY2 = sys.version_info.major == 2
109109
_DEFAULT_MAX_STORAGEACCOUNT_WORKERS = multiprocessing.cpu_count() * 3
110110
_MAX_BLOB_CHUNK_SIZE_BYTES = 4194304
@@ -870,6 +870,32 @@ def delete_blob(
870870
'incorrect status code returned for delete_blob: {}'.format(
871871
response.status_code))
872872

873+
def create_container(
874+
self, container_name, fail_on_exist=False):
875+
"""Create a container
876+
Parameters:
877+
container_name - container name
878+
Returns:
879+
Nothing
880+
Raises:
881+
IOError if unexpected status code
882+
"""
883+
url = '{endpoint}{container_name}{saskey}'.format(
884+
endpoint=self.endpoint, container_name=container_name,
885+
saskey=self.saskey)
886+
reqparams = {'restype': 'container'}
887+
response = azure_request(
888+
requests.put, url=url, params=reqparams, timeout=self.timeout)
889+
if response.status_code != 201:
890+
if response.status_code == 409:
891+
if fail_on_exist:
892+
response.raise_for_status()
893+
else:
894+
return
895+
raise IOError('incorrect status code returned for '
896+
'create_container: {}'.format(
897+
response.status_code))
898+
873899

874900
class StorageChunkWorker(threading.Thread):
875901
"""Chunk worker for a storage entity"""
@@ -1416,7 +1442,7 @@ def azure_request(req, timeout=None, *args, **kwargs):
14161442
while True:
14171443
try:
14181444
return req(*args, **kwargs)
1419-
except requests.Timeout as exc:
1445+
except requests.Timeout:
14201446
pass
14211447
except (requests.ConnectionError,
14221448
requests.exceptions.ChunkedEncodingError) as exc:
@@ -2160,8 +2186,18 @@ def main():
21602186
account_name=args.storageaccount,
21612187
sas_token=args.saskey,
21622188
endpoint_suffix=args.endpoint)
2163-
# disable container/share creation (not possible with SAS)
2164-
args.createcontainer = False
2189+
# disable container/share creation if SAS is not account-level and
2190+
# does not contain a signed resource type with container-level access
2191+
if args.createcontainer:
2192+
args.createcontainer = False
2193+
sasparts = args.saskey.split('&')
2194+
for part in sasparts:
2195+
tmp = part.split('=')
2196+
if tmp[0] == 'srt':
2197+
if 'c' in tmp[1]:
2198+
args.createcontainer = True
2199+
break
2200+
del sasparts
21652201
if blob_service is None:
21662202
raise ValueError('blob_service is invalid')
21672203
if args.fileshare and file_service is None:

test/test_blobxfer.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,22 @@ def test_sasblobservice_createblob():
433433
sbs.create_blob('container', 'blob', 0, cs)
434434

435435

436+
def test_sasblobservice_createcontainer():
437+
session = requests.Session()
438+
adapter = requests_mock.Adapter()
439+
session.mount('mock', adapter)
440+
441+
with requests_mock.mock() as m:
442+
m.put('mock://blobepcontainer?saskey', status_code=201)
443+
sbs = blobxfer.SasBlobService('mock://blobep', 'saskey', None)
444+
sbs.create_container('container', fail_on_exist=False)
445+
446+
m.put('mock://blobepcontainer?saskey', status_code=409)
447+
sbs = blobxfer.SasBlobService('mock://blobep', 'saskey', None)
448+
with pytest.raises(requests.exceptions.HTTPError):
449+
sbs.create_container('container', fail_on_exist=True)
450+
451+
436452
def test_storagechunkworker_run(tmpdir):
437453
lpath = str(tmpdir.join('test.tmp'))
438454
with open(lpath, 'wt') as f:
@@ -1050,6 +1066,17 @@ def test_main1(
10501066
args.managementcert = None
10511067
args.managementep = None
10521068
args.subscriptionid = None
1069+
1070+
args.upload = False
1071+
args.download = True
1072+
args.remoteresource = None
1073+
args.saskey = 'saskey&srt=c'
1074+
with pytest.raises(ValueError):
1075+
blobxfer.main()
1076+
args.upload = True
1077+
args.download = False
1078+
args.saskey = None
1079+
10531080
os.environ[blobxfer._ENVVAR_SASKEY] = 'saskey'
10541081
with open(lpath, 'wt') as f:
10551082
f.write(str(uuid.uuid4()))

0 commit comments

Comments
 (0)