| # Copyright 2015 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import logging |
| import os |
| |
| from py_utils import cloud_storage |
| |
| from dependency_manager import exceptions |
| |
| |
| BACKUP_PATH_EXTENSION = 'old' |
| |
| |
| class CloudStorageUploader(object): |
| def __init__(self, bucket, remote_path, local_path, cs_backup_path=None): |
| if not bucket or not remote_path or not local_path: |
| raise ValueError( |
| 'Attempted to partially initialize upload data with bucket %s, ' |
| 'remote_path %s, and local_path %s', bucket, remote_path, local_path) |
| if not os.path.exists(local_path): |
| raise ValueError('Attempting to initilize UploadInfo with missing ' |
| 'local path %s', local_path) |
| |
| self._cs_bucket = bucket |
| self._cs_remote_path = remote_path |
| self._local_path = local_path |
| self._cs_backup_path = (cs_backup_path or |
| '%s.%s' % (self._cs_remote_path, |
| BACKUP_PATH_EXTENSION)) |
| self._updated = False |
| self._backed_up = False |
| |
| def Upload(self, force=False): |
| """Upload all pending files and then write the updated config to disk. |
| |
| Will attempt to copy files existing in the upload location to a backup |
| location in the same bucket in cloud storage if |force| is True. |
| |
| Args: |
| force: True if files should be uploaded to cloud storage even if a |
| file already exists in the upload location. |
| |
| Raises: |
| CloudStorageUploadConflictError: If |force| is False and the potential |
| upload location of a file already exists. |
| CloudStorageError: If copying an existing file to the backup location |
| or uploading the new file fails. |
| """ |
| if cloud_storage.Exists(self._cs_bucket, self._cs_remote_path): |
| if not force: |
| #pylint: disable=nonstandard-exception |
| raise exceptions.CloudStorageUploadConflictError(self._cs_bucket, |
| self._cs_remote_path) |
| #pylint: enable=nonstandard-exception |
| logging.debug('A file already exists at upload path %s in self.cs_bucket' |
| ' %s', self._cs_remote_path, self._cs_bucket) |
| try: |
| cloud_storage.Copy(self._cs_bucket, self._cs_bucket, |
| self._cs_remote_path, self._cs_backup_path) |
| self._backed_up = True |
| except cloud_storage.CloudStorageError: |
| logging.error('Failed to copy existing file %s in cloud storage bucket ' |
| '%s to backup location %s', self._cs_remote_path, |
| self._cs_bucket, self._cs_backup_path) |
| raise |
| |
| try: |
| cloud_storage.Insert( |
| self._cs_bucket, self._cs_remote_path, self._local_path) |
| except cloud_storage.CloudStorageError: |
| logging.error('Failed to upload %s to %s in cloud_storage bucket %s', |
| self._local_path, self._cs_remote_path, self._cs_bucket) |
| raise |
| self._updated = True |
| |
| def Rollback(self): |
| """Attempt to undo the previous call to Upload. |
| |
| Does nothing if no previous call to Upload was made, or if nothing was |
| successfully changed. |
| |
| Returns: |
| True iff changes were successfully rolled back. |
| Raises: |
| CloudStorageError: If copying the backed up file to its original |
| location or removing the uploaded file fails. |
| """ |
| cloud_storage_changed = False |
| if self._backed_up: |
| cloud_storage.Copy(self._cs_bucket, self._cs_bucket, self._cs_backup_path, |
| self._cs_remote_path) |
| cloud_storage_changed = True |
| self._cs_backup_path = None |
| elif self._updated: |
| cloud_storage.Delete(self._cs_bucket, self._cs_remote_path) |
| cloud_storage_changed = True |
| self._updated = False |
| return cloud_storage_changed |
| |
| def __eq__(self, other, msg=None): |
| if type(self) != type(other): |
| return False |
| return (self._local_path == other._local_path and |
| self._cs_remote_path == other._cs_remote_path and |
| self._cs_bucket == other._cs_bucket) |
| |