# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- # # Copyright 2013 Matthieu Huin # Copyright 2015 Scott McKenzie # # This file is part of duplicity. # # Duplicity is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the # Free Software Foundation; either version 2 of the License, or (at your # option) any later version. # # Duplicity is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with duplicity; if not, write to the Free Software Foundation, # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import os import duplicity.backend from duplicity import log from duplicity.errors import BackendException class AzureBackend(duplicity.backend.Backend): """ Backend for Azure Blob Storage Service """ def __init__(self, parsed_url): duplicity.backend.Backend.__init__(self, parsed_url) if 'AZURE_ACCOUNT_NAME' not in os.environ: raise BackendException('AZURE_ACCOUNT_NAME environment variable not set.') if 'AZURE_ACCOUNT_KEY' not in os.environ: raise BackendException('AZURE_ACCOUNT_KEY environment variable not set.') self.dataTier = os.environ['AZURE_DATA_TIER'] if 'AZURE_DATA_TIER' in os.environ else None self.manifestTier = os.environ['AZURE_MANIFEST_TIER'] if 'AZURE_MANIFEST_TIER' in os.environ else None # TODO: validate container name self.container = parsed_url.path.lstrip('/') self._init_blob_service(os.environ['AZURE_ACCOUNT_NAME'], os.environ['AZURE_ACCOUNT_KEY'], self.container) try: self.blob_service.create_container() except self.AzureConflictError: # Indicates that the resource could not be created because it already exists. pass except Exception as e: log.FatalError("Could not create Azure container: %s" % unicode(e.message).split('\n', 1)[0], log.ErrorCode.connection_failed) def _init_blob_service(self, account_name, account_key, container): # Import Microsoft Azure Storage SDK for Python library. try: import azure import azure.storage if hasattr(azure.storage, 'BlobService'): # v0.11.1 and below from azure.storage import BlobService self.AzureMissingResourceError = azure.WindowsAzureMissingResourceError self.AzureConflictError = azure.WindowsAzureConflictError self.blob_service = BlobServiceAdapter( BlobService(account_name=account_name, account_key=account_key), container ) else: import azure.storage.blob if hasattr(azure.storage.blob, 'BlobService'): from azure.storage.blob import BlobService self.blob_service = BlobServiceAdapter( BlobService(account_name=account_name, account_key=account_key), container ) else: from azure.storage.blob.blockblobservice import BlockBlobService self.blob_service = BlockBlobServiceAdapter( BlockBlobService(account_name=account_name, account_key=account_key), container ) self.AzureMissingResourceError = azure.common.AzureMissingResourceHttpError self.AzureConflictError = azure.common.AzureConflictHttpError except ImportError as e: raise BackendException("""\ Azure backend requires Microsoft Azure Storage SDK for Python (https://pypi.python.org/pypi/azure-storage/). Exception: %s""" % str(e)) def _put(self, source_path, remote_filename): self.blob_service.put(source_path, remote_filename) self._set_tier(remote_filename) def _set_tier(self, remote_filename): tier = self.manifestTier if self._is_manifest(remote_filename) else self.dataTier self.blob_service.set_tier(remote_filename, tier) def _is_manifest(self, filename): return filename.endswith('manifest.gpg') or filename.endswith('manifest.gpg') def _get(self, remote_filename, local_path): self.blob_service.get(remote_filename, local_path) def _list(self): return self.blob_service.list() def _delete(self, filename): self.blob_service.delete(filename) def _query(self, filename): return self.blob_service.query(filename) def _error_code(self, operation, e): if isinstance(e, self.AzureMissingResourceError): return log.ErrorCode.backend_not_found class BlobServiceAdapter(object): def __init__(self, blob_service, container): self.blob_service = blob_service self.container = container def create_container(self): self.blob_service.create_container(self.container, fail_on_exist=True) def put(self, source_path, remote_filename): # https://azure.microsoft.com/en-us/documentation/articles/storage-python-how-to-use-blob-storage/#upload-a-blob-into-a-container self.blob_service.put_block_blob_from_path(self.container, remote_filename, source_path.name) def get(self, remote_filename, local_path): # https://azure.microsoft.com/en-us/documentation/articles/storage-python-how-to-use-blob-storage/#download-blobs self.blob_service.get_blob_to_path(self.container, remote_filename, local_path.name) def set_tier(self, remote_filename, tier): # TODO: set storage tier with StorageService API pass def list(self): # https://azure.microsoft.com/en-us/documentation/articles/storage-python-how-to-use-blob-storage/#list-the-blobs-in-a-container blobs = [] marker = None while True: batch = self.blob_service.list_blobs(self.container, marker=marker) blobs.extend(batch) if not batch.next_marker: break marker = batch.next_marker return [blob.name for blob in blobs] def delete(self, filename): # http://azure.microsoft.com/en-us/documentation/articles/storage-python-how-to-use-blob-storage/#delete-blobs self.blob_service.delete_blob(self.container, filename) def query(self, filename): prop = self.blob_service.get_blob_properties(self.container, filename) return {'size': int(prop['content-length'])} class BlockBlobServiceAdapter(BlobServiceAdapter): def __init__(self, blob_service, container): BlobServiceAdapter.__init__(self, blob_service, container) def put(self, source_path, remote_filename): # https://azure-storage.readthedocs.io/ref/azure.storage.blob.blockblobservice.html#azure.storage.blob.blockblobservice.BlockBlobService.create_blob_from_path self.blob_service.create_blob_from_path(self.container, remote_filename, source_path.name) def set_tier(self, remote_filename, tier): # https://azure-storage.readthedocs.io/ref/azure.storage.blob.blockblobservice.html#azure.storage.blob.blockblobservice.BlockBlobService.set_standard_blob_tier if tier is not None: self.blob_service.set_standard_blob_tier(self.container, remote_filename, tier) def query(self, filename): # https://azure-storage.readthedocs.io/ref/azure.storage.blob.baseblobservice.html#azure.storage.blob.baseblobservice.BaseBlobService.get_blob_properties prop = self.blob_service.get_blob_properties(self.container, filename) return {'size': prop.properties.content_length } duplicity.backend.register_backend('azure', AzureBackend)