Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(80)

Unified Diff: third_party/gsutil/20110627/gslib/command.py

Issue 10199002: Upgrade gsutil to 3.4 (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Addressed comments Created 8 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « third_party/gsutil/20110627/gslib/__init__.py ('k') | third_party/gsutil/20110627/gslib/exception.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: third_party/gsutil/20110627/gslib/command.py
diff --git a/third_party/gsutil/20110627/gslib/command.py b/third_party/gsutil/20110627/gslib/command.py
deleted file mode 100755
index 4bb3c43526fa0ab0cd8a59e6be5b217f18ee0ba9..0000000000000000000000000000000000000000
--- a/third_party/gsutil/20110627/gslib/command.py
+++ /dev/null
@@ -1,1890 +0,0 @@
-# Copyright 2010 Google Inc.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""Implementation of gsutil commands."""
-
-import ctypes
-import datetime
-import gzip
-import mimetypes
-import os
-import platform
-import re
-import shutil
-import signal
-import sys
-import tarfile
-import tempfile
-import time
-import webbrowser
-import xml.dom.minidom
-import xml.sax.xmlreader
-import boto
-import boto.s3.connection
-
-from boto import handler
-from boto.gs.resumable_upload_handler import ResumableUploadHandler
-from boto.provider import Provider
-from boto.pyami.config import BotoConfigLocations
-from boto.s3.resumable_download_handler import ResumableDownloadHandler
-from boto.storage_uri import BucketStorageUri
-from exception import CommandException
-from gslib.project_id import ProjectIdHandler
-import wildcard_iterator
-from wildcard_iterator import ContainsWildcard
-from wildcard_iterator import ResultType
-from wildcard_iterator import WildcardException
-
-GOOG_API_CONSOLE_URI = "http://code.google.com/apis/console"
-
-_HAVE_OAUTH2 = False
-try:
- from oauth2_plugin import oauth2_helper
- _HAVE_OAUTH2 = True
-except ImportError:
- pass
-
-# Enum class for specifying listing style.
-class ListingStyle(object):
- SHORT = 'SHORT'
- LONG = 'LONG'
- LONG_LONG = 'LONG_LONG'
-
-
-# Binary exponentiation strings.
-EXP_STRINGS = [
- (0, 'B'),
- (10, 'KB'),
- (20, 'MB'),
- (30, 'GB'),
- (40, 'TB'),
- (50, 'PB'),
-]
-
-ONE_MB = 1024*1024
-
-SCOPE_FULL_CONTROL = 'https://www.googleapis.com/auth/devstorage.full_control'
-SCOPE_READ_WRITE = 'https://www.googleapis.com/auth/devstorage.read_write'
-SCOPE_READ_ONLY = 'https://www.googleapis.com/auth/devstorage.read_only'
-
-CONFIG_PRELUDE_CONTENT = """
-# This file contains credentials and other configuration information needed
-# by the boto library, used by gsutil. You can edit this file (e.g., to add
-# credentials) but be careful not to mis-edit any of the variable names (like
-# "gs_access_key_id") or remove important markers (like the "[Credentials]" and
-# "[Boto]" section delimeters).
-#
-"""
-
-CONFIG_BOTO_SECTION_CONTENT = """
-[Boto]
-
-# To use a proxy, edit and uncomment the proxy and proxy_port lines. If you
-# need a user/password with this proxy, edit and uncomment those lines as well.
-#proxy = <proxy host>
-#proxy_port = <proxy port>
-#proxy_user = <your proxy user name>
-#proxy_pass = <your proxy password>
-
-# The following two options control the use of a secure transport for requests
-# to S3 and Google Storage. It is highly recommended to set both options to
-# True in production environments, especially when using OAuth2 bearer token
-# authentication with Google Storage.
-
-# Set 'is_secure' to False to cause boto to connect using HTTP instead of the
-# default HTTPS. This is useful if you want to capture/analyze traffic
-# (e.g., with tcpdump). This option should always be set to True in production
-# environments.
-#is_secure = False
-
-# Set 'https_validate_certificates' to False to disable server certificate
-# checking. This is useful if you want to capture/analyze traffic using an
-# intercepting proxy. This option should always be set to True in production
-# environments.
-# In gsutil, the default for this option is True. *However*, the default for
-# this option in the boto library itself is currently 'False'; it is therefore
-# recommended to always set this option explicitly to True in configuration
-# files.
-https_validate_certificates = True
-
-# 'debug' controls the level of debug messages printed: 0 for none, 1
-# for basic boto debug, 2 for all boto debug plus HTTP requests/responses.
-# Note: 'gsutil -d' sets debug to 2 for that one command run.
-#debug = <0, 1, or 2>
-
-# 'num_retries' controls the number of retry attempts made when errors occur.
-# The default is 5. Note: don't set this value to 0, as it will cause boto to
-# fail when reusing HTTP connections.
-#num_retries = <integer value>
-"""
-
-CONFIG_INPUTLESS_GSUTIL_SECTION_CONTENT = """
-[GSUtil]
-
-# 'resumable_threshold' specifies the smallest file size [bytes] for which
-# resumable Google Storage transfers are attempted. The default is 1048576
-# (1MB).
-#resumable_threshold = 1048576
-
-# 'resumable_tracker_dir' specifies the base location where resumable
-# transfer tracker files are saved. By default they're in ~/.gsutil
-#resumable_tracker_dir = <file path>
-
-# 'default_api_version' specifies the default Google Storage API version to
-# use use. If not set below gsutil defaults to API version 1.
-default_api_version = 2
-"""
-
-CONFIG_OAUTH2_CONFIG_CONTENT = """
-[OAuth2]
-# This section specifies options used with OAuth2 authentication.
-
-# 'token_cache' specifies how the OAuth2 client should cache access tokens.
-# Valid values are:
-# 'in_memory': an in-memory cache is used. This is only useful if the boto
-# client instance (and with it the OAuth2 plugin instance) persists
-# across multiple requests.
-# 'file_system' : access tokens will be cached in the file system, in files
-# whose names include a key derived from the refresh token the access token
-# based on.
-# The default is 'file_system'.
-#token_cache = file_system
-#token_cache = in_memory
-
-# 'token_cache_path_pattern' specifies a path pattern for token cache files.
-# This option is only relevant if token_cache = file_system.
-# The value of this option should be a path, with place-holders '%(key)s' (which
-# will be replaced with a key derived from the refresh token the cached access
-# token was based on), and (optionally), %(uid)s (which will be replaced with
-# the UID of the current user, if available via os.getuid()).
-# Note that the config parser itself interpolates '%' placeholders, and hence
-# the above placeholders need to be escaped as '%%(key)s'.
-# The default value of this option is
-# token_cache_path_pattern = <tmpdir>/oauth2client-tokencache.%%(uid)s.%%(key)s
-# where <tmpdir> is the system-dependent default temp directory.
-
-# The following options specify the OAuth2 client identity and secret that is
-# used when requesting and using OAuth2 tokens. If not specified, a default
-# OAuth2 client for the gsutil tool is used; for uses of the boto library (with
-# OAuth2 authentication plugin) in other client software, it is recommended to
-# use a tool/client-specific OAuth2 client. For more information on OAuth2, see
-# http://code.google.com/apis/accounts/docs/OAuth2.html
-#client_id = <OAuth2 client id>
-#client_secret = <OAuth2 client secret>
-
-# The following options specify the label and endpoint URIs for the OAUth2
-# authorization provider being used. Primarily useful for tool developers.
-#provider_label = Google
-#provider_authorization_uri = https://accounts.google.com/o/oauth2/auth
-#provider_token_uri = https://accounts.google.com/o/oauth2/token
-"""
-
-CONFIG_COMMAND_HELP = """
-Help on the gsutil config command:
- gsutil [-D] config [OPTION]
-
- The gsutil config command obtains access credentials for Google Storage, and
- writes a boto/gsutil configuration file with the obtained credentials.
-
- Unless specified otherwise, the configuration file is written to the default
- config file path '%s'. If the default config file already exists, an attempt
- is made to rename the existing file to a backup file '%s'; if that attempt
- fails the command will exit.
-
- A different destination file can be specified with the -o <file> option (use
- '-o -' to write the config to standard output). If the specified file already
- exists, the command will fail.
-
- By default, gsutil config obtains OAuth2 tokens as follows (for background
- on OAuth2, see http://code.google.com/apis/accounts/docs/OAuth2.html):
- The command asks the user to open a web broswer to a URL for Google's
- OAuth2 authorization page. In the browser, the user will be asked to sign
- into the user's Google Account, unless already signed in. The user is then
- prompted to authorize gsutil to access the user's Google Storage account
- on the user's behalf. If the user approves the request, a verification
- code is shown. The gsutil config command prompts for this verification
- code, which is used to obtain an OAuth2 token that is written to the
- configuration file.
-
- The -b option can be used to instruct gsutil config to launch a browser,
- (using python's webbrowser module) to navigate to Google's OAuth2
- authorization page. Note that this will probably not work as expected
- if you are running gsutil from an ssh window, or using gsutil on Windows.
-
- The -r, -w, -f options cause gsutil config to request a token with restricted
- scope; the resulting token will be restricted to read-only operations,
- read-write operation, or all operations (including getacl/setacl operations).
- In addition, -s <scope> can be used to request additional (non-Google-Storage)
- scopes.
-
- If no explicit scope option is given, -f (full control) is assumed by default.
-
- The -a option can be used to prompt for Google Storage access key and secret
- instead.
-
- Options:
- -h Print this help.
- -a Prompt for Google Storage access key and secret instead of
- obtaining an OAuth2 token.
- -b Launch browser to obtain OAuth2 approval and project ID instead
- of showing the URL and asking user to open the browser.
- -D Print debug output.
- -f Request token with full-control access (default).
- -o <file> Write the configuration to <file> (use '-' for stdout)
- -r Request token restricted to read-only access.
- -s <scope> Request additional OAuth2 <scope>.
- -w Request token restricted to read-write access.
-
-"""
-
-def MakeHumanReadable(num):
- """Generates human readable string for a number.
-
- Args:
- num: the number
-
- Returns:
- A string form of the number using size abbreviations (KB, MB, etc.)
- """
- i = 0
- while i+1 < len(EXP_STRINGS) and num >= (2 ** EXP_STRINGS[i+1][0]):
- i += 1
- rounded_val = round(float(num) / 2 ** EXP_STRINGS[i][0], 2)
- return '%s %s' % (rounded_val, EXP_STRINGS[i][1])
-
-
-def UriStrFor(iterated_uri, obj):
- """Constructs a StorageUri string for the given iterated_uri and object.
-
- For example if we were iterating gs://*, obj could be an object in one
- of the user's buckets enumerated by the ls command.
-
- Args:
- iterated_uri: base StorageUri being iterated.
- obj: object being listed.
-
- Returns:
- URI string.
- """
- return '%s://%s/%s' % (iterated_uri.scheme, obj.bucket.name, obj.name)
-
-
-def OpenConfigFile(file_path):
- """Creates and opens a configuration file for writing.
-
- The file is created with mode 0600, and attempts to open existing files will
- fail (the latter is important to prevent symlink attacks).
-
- It is the caller's responsibility to close the file.
-
- Args:
- file_path: Path of the file to be created.
-
- Returns:
- A writable file object for the opened file.
-
- Raises:
- CommandException: if an error occurred when opening the file (including when
- the file already exists).
- """
- flags = os.O_RDWR | os.O_CREAT | os.O_EXCL
- # Accommodate Windows; stolen from python2.6/tempfile.py.
- if hasattr(os, 'O_NOINHERIT'):
- flags |= os.O_NOINHERIT
- try:
- fd = os.open(file_path, flags, 0600)
- except (OSError, IOError), e:
- raise CommandException("Failed to open %s for writing: %s" %
- (file_path, e))
- return os.fdopen(fd, "w")
-
-
-class Command(object):
- """Class that contains all gsutil command code."""
-
- def __init__(self, gsutil_bin_dir, boto_lib_dir, usage_string,
- config_file_list, bucket_storage_uri_class=BucketStorageUri):
- """Instantiates Command class.
-
- Args:
- gsutil_bin_dir: bin dir from which gsutil is running.
- boto_lib_dir: lib dir where boto runs.
- usage_string: usage string to print when user makes command error.
- config_file_list: config file list returned by GetBotoConfigFileList().
- bucket_storage_uri_class: Class to instantiate for cloud StorageUris.
- Settable for testing/mocking.
- """
- self.gsutil_bin_dir = gsutil_bin_dir
- self.usage_string = usage_string
- self.boto_lib_dir = boto_lib_dir
- self.config_file_list = config_file_list
- self.bucket_storage_uri_class = bucket_storage_uri_class
-
- config = boto.config
- self.proj_id_handler = ProjectIdHandler()
-
- def OutputUsageAndExit(self):
- sys.stderr.write(self.usage_string)
- sys.exit(0)
-
- def StorageUri(self, uri_str, debug=0, validate=True):
- """
- Helper to instantiate boto.StorageUri with gsutil default flag values.
- Uses self.bucket_storage_uri_class to support mocking/testing.
-
- Args:
- uri_str: StorageUri naming bucket + optional object.
- debug: debug level to pass in to boto connection (range 0..3).
- validate: Whether to check for bucket name validity.
-
- Returns:
- boto.StorageUri for given uri_str.
-
- Raises:
- InvalidUriError: if uri_str not valid.
- """
- return boto.storage_uri(
- uri_str, 'file', debug=debug, validate=validate,
- bucket_storage_uri_class=self.bucket_storage_uri_class)
-
- def CmdWildcardIterator(self, uri_or_str, result_type=ResultType.URIS,
- headers=None, debug=0):
- """
- Helper to instantiate gslib.WildcardIterator, passing
- self.bucket_storage_uri_class to support mocking/testing.
- Args are same as gslib.WildcardIterator interface, but without the
- bucket_storage_uri_class param (which is instead filled in from Command
- class state).
- """
- return wildcard_iterator.wildcard_iterator(
- uri_or_str, self.proj_id_handler, result_type=result_type,
- bucket_storage_uri_class=self.bucket_storage_uri_class,
- headers=headers, debug=debug)
-
- def InsistUriNamesContainer(self, command, uri):
- """Checks that URI names a directory or bucket.
-
- Args:
- command: command being run
- uri: StorageUri to check
-
- Raises:
- CommandException: if errors encountered.
- """
- if uri.names_singleton():
- raise CommandException('Destination StorageUri must name a bucket or '
- 'directory for the\nmultiple source form of the '
- '"%s" command.' % command)
-
- def CatCommand(self, args, sub_opts=None, headers=None, debug=0):
- """Implementation of cat command.
-
- Args:
- args: command-line argument list.
- sub_opts: list of command-specific options from getopt.
- headers: dictionary containing optional HTTP headers to pass to boto.
- debug: debug level to pass in to boto connection (range 0..3).
-
- Raises:
- CommandException: if errors encountered.
- """
- show_header = False
- if sub_opts:
- for o, unused_a in sub_opts:
- if o == '-h':
- show_header = True
-
- printed_one = False
- for uri_str in args:
- for uri in self.CmdWildcardIterator(uri_str, headers=headers,
- debug=debug):
- if not uri.object_name:
- raise CommandException('"cat" command must specify objects.')
- if show_header:
- if printed_one:
- print
- print '==> %s <==' % uri.__str__()
- printed_one = True
- tmp_file = tempfile.TemporaryFile()
- key = uri.get_key(False, headers)
- key.get_file(tmp_file, headers)
- tmp_file.seek(0)
- while True:
- # Use 8k buffer size.
- data = tmp_file.read(8192)
- if not data:
- break
- sys.stdout.write(data)
- tmp_file.close()
-
- def SetAclCommand(self, args, unused_sub_opts=None, headers=None, debug=0):
- """Implementation of setacl command.
-
- Args:
- args: command-line argument list.
- unused_sub_opts: list of command-specific options from getopt.
- headers: dictionary containing optional HTTP headers to pass to boto.
- debug: debug level to pass in to boto connection (range 0..3).
-
- Raises:
- CommandException: if errors encountered.
- """
- acl_arg = args[0]
- uri_args = args[1:]
- provider = None
- first_uri = None
- # Do a first pass over all matched objects to disallow multi-provider
- # setacl requests, because there are differences in the ACL models.
- for uri_str in uri_args:
- for uri in self.CmdWildcardIterator(uri_str, headers=headers,
- debug=debug):
- if not provider:
- provider = uri.scheme
- elif uri.scheme != provider:
- raise CommandException('"setacl" command spanning providers not '
- 'allowed.')
- if not first_uri:
- first_uri = uri
-
- # Get ACL object from connection for the first URI, for interpreting the
- # ACL. This won't fail because the main startup code insists on 1 arg
- # for this command.
- storage_uri = first_uri
- acl_class = storage_uri.acl_class()
- canned_acls = storage_uri.canned_acls()
-
- # Determine whether acl_arg names a file containing XML ACL text vs. the
- # string name of a canned ACL.
- if os.path.isfile(acl_arg):
- acl_file = open(acl_arg, 'r')
- acl_txt = acl_file.read()
- acl_file.close()
- acl_obj = acl_class()
- h = handler.XmlHandler(acl_obj, storage_uri.get_bucket())
- try:
- xml.sax.parseString(acl_txt, h)
- except xml.sax._exceptions.SAXParseException, e:
- raise CommandException('Requested ACL is invalid: %s at line %s, '
- 'column %s' % (e.getMessage(), e.getLineNumber(),
- e.getColumnNumber()))
- acl_arg = acl_obj
- else:
- # No file exists, so expect a canned ACL string.
- if acl_arg not in canned_acls:
- raise CommandException('Invalid canned ACL "%s".' % acl_arg)
-
- # Now iterate over URIs and set the ACL on each.
- for uri_str in uri_args:
- for uri in self.CmdWildcardIterator(uri_str, headers=headers,
- debug=debug):
- print 'Setting ACL on %s...' % uri
- uri.set_acl(acl_arg, uri.object_name, False, headers)
-
- def ExplainIfSudoNeeded(self, tf, dirs_to_remove):
- """Explains what to do if sudo needed to update gsutil software.
-
- Happens if gsutil was previously installed by a different user (typically if
- someone originally installed in a shared file system location, using sudo).
-
- Args:
- tf: opened TarFile.
- dirs_to_remove: list of directories to remove.
-
- Raises:
- CommandException: if errors encountered.
- """
- system = platform.system()
- # If running under Windows we don't need (or have) sudo.
- if system.lower().startswith('windows'):
- return
-
- user_id = os.getuid()
- if (os.stat(self.gsutil_bin_dir).st_uid == user_id and
- os.stat(self.boto_lib_dir).st_uid == user_id):
- return
-
- # Won't fail - this command runs after main startup code that insists on
- # having a config file.
- config_file = self.config_file_list
- self.CleanUpUpdateCommand(tf, dirs_to_remove)
- raise CommandException(
- ('Since it was installed by a different user previously, you will need '
- 'to update using the following commands.\nYou will be prompted for '
- 'your password, and the install will run as "root". If you\'re unsure '
- 'what this means please ask your system administrator for help:'
- '\n\tchmod 644 %s\n\tsudo env BOTO_CONFIG=%s gsutil update'
- '\n\tchmod 600 %s') % (config_file, config_file, config_file),
- informational=True)
-
- # This list is checked during gsutil update by doing a lowercased
- # slash-left-stripped check. For example "/Dev" would match the "dev" entry.
- unsafe_update_dirs = [
- 'applications', 'auto', 'bin', 'boot', 'desktop', 'dev',
- 'documents and settings', 'etc', 'export', 'home', 'kernel', 'lib',
- 'lib32', 'library', 'lost+found', 'mach_kernel', 'media', 'mnt', 'net',
- 'null', 'network', 'opt', 'private', 'proc', 'program files', 'python',
- 'root', 'sbin', 'scripts', 'srv', 'sys', 'system', 'tmp', 'users', 'usr',
- 'var', 'volumes', 'win', 'win32', 'windows', 'winnt',
- ]
-
- def EnsureDirsSafeForUpdate(self, dirs):
- """Throws Exception if any of dirs is known to be unsafe for gsutil update.
-
- This provides a fail-safe check to ensure we don't try to overwrite
- or delete any important directories. (That shouldn't happen given the
- way we construct tmp dirs, etc., but since the gsutil update cleanup
- use shutil.rmtree() it's prudent to add extra checks.)
-
- Args:
- dirs: list of directories to check.
-
- Raises:
- CommandException: If unsafe directory encountered.
- """
- for d in dirs:
- if not d:
- d = 'null'
- if d.lstrip(os.sep).lower() in self.unsafe_update_dirs:
- raise CommandException('EnsureDirsSafeForUpdate: encountered unsafe '
- 'directory (%s); aborting update' % d)
-
- def CleanUpUpdateCommand(self, tf, dirs_to_remove):
- """Cleans up temp files etc. from running update command.
-
- Args:
- tf: opened TarFile.
- dirs_to_remove: list of directories to remove.
-
- """
- tf.close()
- self.EnsureDirsSafeForUpdate(dirs_to_remove)
- for directory in dirs_to_remove:
- shutil.rmtree(directory)
-
- def LoadVersionString(self):
- """Loads version string for currently installed gsutil command.
-
- Returns:
- Version string.
-
- Raises:
- CommandException: if errors encountered.
- """
- ver_file_path = self.gsutil_bin_dir + os.sep + 'VERSION'
- if not os.path.isfile(ver_file_path):
- raise CommandException(
- '%s not found. Did you install the\ncomplete gsutil software after '
- 'the gsutil "update" command was implemented?' % ver_file_path)
- ver_file = open(ver_file_path, 'r')
- installed_version_string = ver_file.read().rstrip('\n')
- ver_file.close()
- return installed_version_string
-
- def UpdateCommand(self, unused_args, sub_opts=None, headers=None, debug=0):
- """Implementation of experimental update command.
-
- Args:
- unused_args: command-line argument list.
- sub_opts: list of command-specific options from getopt.
- headers: dictionary containing optional HTTP headers to pass to boto.
- debug: debug level to pass in to boto connection (range 0..3).
-
- Raises:
- CommandException: if errors encountered.
- """
- installed_version_string = self.LoadVersionString()
-
- dirs_to_remove = []
- # Retrieve gsutil tarball and check if it's newer than installed code.
- # TODO: Store this version info as metadata on the tarball object and
- # change this command's implementation to check that metadata instead of
- # downloading the tarball to check the version info.
- tmp_dir = tempfile.mkdtemp()
- dirs_to_remove.append(tmp_dir)
- os.chdir(tmp_dir)
- print 'Checking for software update...'
- self.CopyObjsCommand(['gs://pub/gsutil.tar.gz', 'file://gsutil.tar.gz'], [],
- headers, debug)
- tf = tarfile.open('gsutil.tar.gz')
- tf.errorlevel = 1 # So fatal tarball unpack errors raise exceptions.
- tf.extract('./gsutil/VERSION')
- ver_file = open('gsutil/VERSION', 'r')
- latest_version_string = ver_file.read().rstrip('\n')
- ver_file.close()
-
- # The force_update option works around a problem with the way the
- # first gsutil "update" command exploded the gsutil and boto directories,
- # which didn't correctly install boto. People running that older code can
- # run "gsutil update" (to update to the newer gsutil update code) followed
- # by "gsutil update -f" (which will then update the boto code, even though
- # the VERSION is already the latest version).
- force_update = False
- if sub_opts:
- for o, unused_a in sub_opts:
- if o == '-f':
- force_update = True
- if not force_update and installed_version_string == latest_version_string:
- self.CleanUpUpdateCommand(tf, dirs_to_remove)
- raise CommandException('You have the latest version of gsutil installed.',
- informational=True)
-
- print(('This command will update to the "%s" version of\ngsutil at %s') %
- (latest_version_string, self.gsutil_bin_dir))
- self.ExplainIfSudoNeeded(tf, dirs_to_remove)
-
- answer = raw_input('Proceed (Note: experimental command)? [y/N] ')
- if not answer or answer.lower()[0] != 'y':
- self.CleanUpUpdateCommand(tf, dirs_to_remove)
- raise CommandException('Not running update.', informational=True)
-
- # Ignore keyboard interrupts during the update to reduce the chance someone
- # hitting ^C leaves gsutil in a broken state.
- signal.signal(signal.SIGINT, signal.SIG_IGN)
-
- # gsutil_bin_dir lists the path where the code should end up (like
- # /usr/local/gsutil), which is one level down from the relative path in the
- # tarball (since the latter creates files in ./gsutil). So, we need to
- # extract at the parent directory level.
- gsutil_bin_parent_dir = os.path.dirname(self.gsutil_bin_dir)
-
- # Extract tarball to a temporary directory in a sibling to gsutil_bin_dir.
- old_dir = tempfile.mkdtemp(dir=gsutil_bin_parent_dir)
- new_dir = tempfile.mkdtemp(dir=gsutil_bin_parent_dir)
- dirs_to_remove.append(old_dir)
- dirs_to_remove.append(new_dir)
- self.EnsureDirsSafeForUpdate(dirs_to_remove)
- try:
- tf.extractall(path=new_dir)
- except Exception, e:
- self.CleanUpUpdateCommand(tf, dirs_to_remove)
- raise CommandException('Update failed: %s.' % e)
-
- # Move old installation aside and new into place.
- os.rename(self.gsutil_bin_dir, old_dir + os.sep + 'old')
- os.rename(new_dir + os.sep + 'gsutil', self.gsutil_bin_dir)
- self.CleanUpUpdateCommand(tf, dirs_to_remove)
- signal.signal(signal.SIGINT, signal.SIG_DFL)
- print 'Update complete.'
-
- def CheckForDirFileConflict(self, src_uri, dst_path):
- """Checks whether copying src_uri into dst_path is not possible.
-
- This happens if a directory exists in local file system where a file
- needs to go or vice versa. In that case we print an error message and
- exits. Example: if the file "./x" exists and you try to do:
- gsutil cp gs://mybucket/x/y .
- the request can't succeed because it requires a directory where
- the file x exists.
-
- Args:
- src_uri: source StorageUri of copy
- dst_path: destination path.
-
- Raises:
- CommandException: if errors encountered.
- """
- final_dir = os.path.dirname(dst_path)
- if os.path.isfile(final_dir):
- raise CommandException('Cannot retrieve %s because it a file exists '
- 'where a directory needs to be created (%s).' %
- (src_uri, final_dir))
- if os.path.isdir(dst_path):
- raise CommandException('Cannot retrieve %s because a directory exists '
- '(%s) where the file needs to be created.' %
- (src_uri, dst_path))
-
- def GetAclCommand(self, args, unused_sub_opts=None, headers=None, debug=0):
- """Implementation of getacl command.
-
- Args:
- args: command-line argument list.
- unused_sub_opts: list of command-specific options from getopt.
- headers: dictionary containing optional HTTP headers to pass to boto.
- debug: debug level to pass in to boto connection (range 0..3).
-
- Raises:
- CommandException: if errors encountered.
- """
- # Wildcarding is allowed but must resolve to just one object.
- uris = list(self.CmdWildcardIterator(args[0], headers=headers,
- debug=debug))
- if len(uris) != 1:
- raise CommandException('Wildcards must resolve to exactly one object for '
- '"getacl" command.')
- uri = uris[0]
- if not uri.bucket_name:
- raise CommandException('"getacl" command must specify a bucket or '
- 'object.')
- acl = uri.get_acl(False, headers)
- # Pretty-print the XML to make it more easily human editable.
- parsed_xml = xml.dom.minidom.parseString(acl.to_xml().encode('utf-8'))
- print parsed_xml.toprettyxml(indent=' ')
-
- class FileCopyCallbackHandler(object):
- """Outputs progress info for large copy requests."""
-
- def __init__(self, upload):
- if upload:
- self.announce_text = 'Uploading'
- else:
- self.announce_text = 'Downloading'
-
- def call(self, total_bytes_transferred, total_size):
- sys.stderr.write('%s: %s/%s \r' % (
- self.announce_text,
- MakeHumanReadable(total_bytes_transferred),
- MakeHumanReadable(total_size)))
- if total_bytes_transferred == total_size:
- sys.stderr.write('\n')
-
- def GetTransferHandlers(self, uri, key, file_size, upload):
- """
- Selects upload/download and callback handlers.
-
- We use a callback handler that shows a simple textual progress indicator
- if file_size is above the configurable threshold.
-
- We use a resumable transfer handler if file_size is >= the configurable
- threshold and resumable transfers are supported by the given provider.
- boto supports resumable downloads for all providers, but resumable
- uploads are currently only supported by GS.
- """
- config = boto.config
- resumable_threshold = config.getint('GSUtil', 'resumable_threshold', ONE_MB)
- if file_size >= resumable_threshold:
- cb = self.FileCopyCallbackHandler(upload).call
- num_cb = int(file_size / ONE_MB)
- resumable_tracker_dir = config.get(
- 'GSUtil', 'resumable_tracker_dir',
- os.path.expanduser('~' + os.sep + '.gsutil'))
- if not os.path.exists(resumable_tracker_dir):
- os.makedirs(resumable_tracker_dir)
- if upload:
- # Encode the src bucket and key into the tracker file name.
- res_tracker_file_name = (
- re.sub('[/\\\\]', '_', 'resumable_upload__%s__%s.url' %
- (key.bucket.name, key.name)))
- else:
- # Encode the fully-qualified src file name into the tracker file name.
- res_tracker_file_name = (
- re.sub('[/\\\\]', '_', 'resumable_download__%s.etag' %
- (os.path.realpath(uri.object_name))))
- tracker_file = '%s%s%s' % (resumable_tracker_dir, os.sep,
- res_tracker_file_name)
- if upload:
- if uri.scheme == 'gs':
- transfer_handler = ResumableUploadHandler(tracker_file)
- else:
- transfer_handler = None
- else:
- transfer_handler = ResumableDownloadHandler(tracker_file)
- else:
- transfer_handler = None
- cb = None
- num_cb = None
- return (cb, num_cb, transfer_handler)
-
- def CopyObjToObjSameProvider(self, src_key, src_uri, dst_uri, headers):
- # Do Object -> object copy within same provider (uses
- # x-<provider>-copy-source metadata HTTP header to request copying at the
- # server). (Note: boto does not currently provide a way to pass canned_acl
- # when copying from object-to-object through x-<provider>-copy-source)
- src_bucket = src_uri.get_bucket(False, headers)
- dst_bucket = dst_uri.get_bucket(False, headers)
- start_time = time.time()
- dst_bucket.copy_key(dst_uri.object_name, src_bucket.name,
- src_uri.object_name, headers)
- end_time = time.time()
- return (end_time - start_time, src_key.size)
-
- def CheckFreeSpace(self, path):
- """Return path/drive free space (in bytes)."""
- if platform.system() == 'Windows':
- free_bytes = ctypes.c_ulonglong(0)
- ctypes.windll.kernel32.GetDiskFreeSpaceExW(ctypes.c_wchar_p(path), None,
- None,
- ctypes.pointer(free_bytes))
- return free_bytes.value
- else:
- (_, f_frsize, _, _, f_bavail, _, _, _, _, _) = os.statvfs(path)
- return f_frsize * f_bavail
-
- def PerformResumableUploadIfApplies(self, fp, dst_uri, headers, canned_acl):
- """
- Performs resumable upload if supported by provider and file is above
- threshold, else performs non-resumable upload.
-
- Returns (elapsed_time, bytes_transferred).
- """
- start_time = time.time()
- file_size = os.path.getsize(fp.name)
- dst_key = dst_uri.new_key(False, headers)
- (cb, num_cb, res_upload_handler) = self.GetTransferHandlers(
- dst_uri, dst_key, file_size, True)
- if dst_uri.scheme == 'gs':
- # Resumable upload protocol is Google Storage-specific.
- dst_key.set_contents_from_file(fp, headers, policy=canned_acl,
- cb=cb, num_cb=num_cb,
- res_upload_handler=res_upload_handler)
- else:
- dst_key.set_contents_from_file(fp, headers, policy=canned_acl,
- cb=cb, num_cb=num_cb)
- if res_upload_handler:
- bytes_transferred = file_size - res_upload_handler.upload_start_point
- else:
- bytes_transferred = file_size
- end_time = time.time()
- return (end_time - start_time, bytes_transferred)
-
- def UploadFileToObject(self, sub_opts, src_key, src_uri, dst_uri, headers,
- debug):
- gzip_exts = []
- canned_acl = None
- if sub_opts:
- for o, a in sub_opts:
- if o == '-a':
- canned_acls = dst_uri.canned_acls()
- if a not in canned_acls:
- raise CommandException('Invalid canned ACL "%s".' % a)
- canned_acl = a
- elif o == '-t':
- mimetype_tuple = mimetypes.guess_type(src_uri.object_name)
- mime_type = mimetype_tuple[0]
- content_encoding = mimetype_tuple[1]
- if mime_type:
- headers['Content-Type'] = mime_type
- print '\t[Setting Content-Type=%s]' % mime_type
- else:
- print '\t[Unknown content type -> using application/octet stream]'
- if content_encoding:
- headers['Content-Encoding'] = content_encoding
- elif o == '-z':
- gzip_exts = a.split(',')
- fname_parts = src_uri.object_name.split('.')
- if len(fname_parts) > 1 and fname_parts[-1] in gzip_exts:
- if debug:
- print 'Compressing %s (to tmp)...' % src_key
- gzip_tmp = tempfile.mkstemp()
- gzip_path = gzip_tmp[1]
- # Check for temp space. Assume the compressed object is at most 2x
- # the size of the object (normally should compress to smaller than
- # the object)
- if self.CheckFreeSpace(gzip_path) < 2*int(os.path.getsize(src_key.name)):
- raise CommandException('Inadequate temp space available to compress '
- '%s' % src_key.name)
- the_gzip = gzip.open(gzip_path, 'wb')
- the_gzip.writelines(src_key.fp)
- the_gzip.close()
- headers['Content-Encoding'] = 'gzip'
- (elapsed_time, bytes_transferred) = self.PerformResumableUploadIfApplies(
- open(gzip_path, 'rb'), dst_uri, headers, canned_acl)
- os.unlink(gzip_path)
- else:
- (elapsed_time, bytes_transferred) = self.PerformResumableUploadIfApplies(
- src_key.fp, dst_uri, headers, canned_acl)
- return (elapsed_time, bytes_transferred)
-
- def DownloadObjectToFile(self, src_key, src_uri, dst_uri, headers, debug):
- (cb, num_cb, res_download_handler) = self.GetTransferHandlers(
- src_uri, src_key, src_key.size, False)
- file_name = dst_uri.object_name
- dir_name = os.path.dirname(file_name)
- if dir_name and not os.path.exists(dir_name):
- os.makedirs(dir_name)
- # For gzipped objects not named *.gz download to a temp file and unzip.
- if (hasattr(src_key, 'content_encoding') and
- src_key.content_encoding == 'gzip' and
- not file_name.endswith('.gz')):
- # We can't use tempfile.mkstemp() here because we need a predictable
- # filename for resumable downloads.
- download_file_name = '%s_.gztmp' % file_name
- need_to_unzip = True
- else:
- download_file_name = file_name
- need_to_unzip = False
- if res_download_handler:
- fp = open(download_file_name, 'ab')
- else:
- fp = open(download_file_name, 'wb')
- start_time = time.time()
- src_key.get_contents_to_file(fp, headers, cb=cb, num_cb=num_cb,
- res_download_handler=res_download_handler)
- fp.close()
- end_time = time.time()
- if res_download_handler:
- bytes_transferred = (
- src_key.size - res_download_handler.download_start_point)
- else:
- bytes_transferred = src_key.size
- if need_to_unzip:
- if debug:
- print 'Uncompressing tmp to %s...' % file_name
- # Downloaded gzipped file to a filename w/o .gz extension, so unzip.
- f_in = gzip.open(download_file_name, 'rb')
- f_out = open(file_name, 'wb')
- f_out.writelines(f_in)
- f_out.close();
- f_in.close();
- os.unlink(download_file_name)
- return (end_time - start_time, bytes_transferred)
-
- def CopyFileToFile(self, src_key, dst_uri, headers):
- dst_key = dst_uri.new_key(False, headers)
- start_time = time.time()
- dst_key.set_contents_from_file(src_key.fp, headers)
- end_time = time.time()
- return (end_time - start_time, os.path.getsize(src_key.fp.name))
-
- def CopyObjToObjDiffProvider(self, sub_opts, src_key, src_uri, dst_uri,
- headers, debug):
- # We implement cross-provider object copy through a local temp file.
- # Note that a downside of this approach is that killing the gsutil
- # process partway through and then restarting will always repeat the
- # download and upload, because the temp file name is different for each
- # incarnation. (If however you just leave the process running and failures
- # happen along the way, they will continue to restart and make progress
- # as long as not too many failures happen in a row with no progress.)
- tmp = tempfile.NamedTemporaryFile()
- if self.CheckFreeSpace(tempfile.tempdir) < src_key.size:
- raise CommandException('Inadequate temp space available to perform the '
- 'requested copy')
- start_time = time.time()
- file_uri = self.StorageUri('file://%s' % tmp.name, debug=debug,
- validate=False)
- try:
- self.DownloadObjectToFile(src_key, src_uri, file_uri, headers, debug)
- self.UploadFileToObject(sub_opts, file_uri.get_key(), file_uri, dst_uri,
- headers, debug)
- finally:
- tmp.close()
- end_time = time.time()
- return (end_time - start_time, src_key.size)
-
- def PerformCopy(self, src_uri, dst_uri, sub_opts=None, headers=None, debug=0):
- """Helper method for CopyObjsCommand.
-
- Args:
- src_uri: source StorageUri.
- dst_uri: destination StorageUri.
- sub_opts: list of command-specific options from getopt.
- headers: dictionary containing optional HTTP headers to pass to boto.
- debug: debug level to pass in to boto connection (range 0..3).
-
- Returns:
- (elapsed_time, bytes_transferred) excluding overhead like initial HEAD.
-
- Raises:
- CommandException: if errors encountered.
- """
- # Make a copy of the input headers each time so we can set a different
- # MIME type for each object.
- if headers:
- headers = headers.copy()
- else:
- headers = {}
-
- src_key = src_uri.get_key(False, headers)
- if not src_key:
- raise CommandException('"%s" does not exist.' % src_uri)
-
- # Separately handle cases to avoid extra file and network copying of
- # potentially very large files/objects.
-
- if src_uri.is_cloud_uri() and dst_uri.is_cloud_uri():
- if src_uri.scheme == dst_uri.scheme:
- return self.CopyObjToObjSameProvider(src_key, src_uri, dst_uri,
- headers)
- else:
- return self.CopyObjToObjDiffProvider(sub_opts, src_key, src_uri,
- dst_uri, headers, debug)
- elif src_uri.is_file_uri() and dst_uri.is_cloud_uri():
- return self.UploadFileToObject(sub_opts, src_key, src_uri, dst_uri,
- headers, debug)
- elif src_uri.is_cloud_uri() and dst_uri.is_file_uri():
- return self.DownloadObjectToFile(src_key, src_uri, dst_uri, headers,
- debug)
- elif src_uri.is_file_uri() and dst_uri.is_file_uri():
- return self.CopyFileToFile(src_key, dst_uri, headers)
- else:
- raise CommandException('Unexpected src/dest case')
-
- def ExpandWildcardsAndContainers(self, uri_strs, sub_opts=None, headers=None,
- debug=0):
- """Expands URI wildcarding, object-less bucket names, and directory names.
-
- Examples:
- Calling with uri_strs='gs://bucket' will enumerate all contained objects.
- Calling with uri_strs='file:///tmp' will enumerate all files under /tmp
- (or under any subdirectory).
- The previous example is equivalent to uri_strs='file:///tmp/*'
- and to uri_strs='file:///tmp/**'
-
- Args:
- uri_strs: URI strings needing expansion
- sub_opts: list of command-specific options from getopt.
- headers: dictionary containing optional HTTP headers to pass to boto.
- debug: debug level to pass in to boto connection (range 0..3).
-
- Returns:
- dict mapping StorageUri -> list of StorageUri, for each input uri_str.
-
- We build a dict of the expansion instead of using a generator to
- iterate incrementally because caller needs to know count before
- iterating and performing copy operations.
- """
- # The algorithm we use is:
- # 1. Build a first level expanded list from uri_strs consisting of all
- # URIs that aren't file wildcards, plus expansions of the file wildcards.
- # 2. Build dict from above expanded list.
- # We do so that we can properly handle the following example:
- # gsutil cp file0 dir0 gs://bucket
- # where dir0 contains file1 and dir1/file2.
- # If we didn't do the first expansion, this cp command would end up
- # with this expansion:
- # {file://file0:[file://file0],file://dir0:[file://dir0/file1,
- # file://dir0/dir1/file2]}
- # instead of the (correct) expansion:
- # {file://file0:[file://file0],file://dir0/file1:[file://dir0/file1],
- # file://dir0/dir1:[file://dir0/dir1/file2]}
- # The latter expansion is needed so that in the "Copying..." loop of
- # CopyObjsCommand we know that dir0 was being copied, so we create an
- # object called gs://bucket/dir0/dir1/file2. (Otherwise it would look
- # like a single file was being copied, so we'd create an object called
- # gs://bucket/file2.)
-
- should_recurse = False
- if sub_opts:
- for o, unused_a in sub_opts:
- if o == '-r' or o == '-R':
- should_recurse = True
-
- # Step 1.
- uris_to_expand = []
- for uri_str in uri_strs:
- uri = self.StorageUri(uri_str, debug=debug, validate=False)
- if uri.is_file_uri() and ContainsWildcard(uri_str):
- uris_to_expand.extend(list(
- self.CmdWildcardIterator(uri, headers=headers, debug=debug)))
- else:
- uris_to_expand.append(uri)
-
- # Step 2.
- result = {}
- for uri in uris_to_expand:
- if uri.names_container():
- if not should_recurse:
- if uri.is_file_uri():
- desc = 'directory'
- else:
- desc = 'bucket'
- print 'Omitting %s "%s".' % (desc, uri.uri)
- result[uri] = []
- continue
- if uri.is_file_uri():
- # dir -> convert to implicit recursive wildcard.
- uri_to_iter = '%s/**' % uri.uri
- else:
- # bucket -> convert to implicit wildcard.
- uri_to_iter = uri.clone_replace_name('*')
- else:
- uri_to_iter = uri
- result[uri] = list(self.CmdWildcardIterator(
- uri_to_iter, headers=headers, debug=debug))
- return result
-
- def ErrorCheckCopyRequest(self, src_uri_expansion, dst_uri_str, headers,
- debug, command='cp'):
- """Checks copy request for problems, and builds needed base_dst_uri.
-
- base_dst_uri is the base uri to be used if it's a multi-object copy, e.g.,
- the URI for the destination bucket. The actual dst_uri can then be
- constructed from the src_uri and this base_dst_uri.
-
- Args:
- src_uri_expansion: result from ExpandWildcardsAndContainers call.
- dst_uri_str: string representation of destination StorageUri.
- headers: dictionary containing optional HTTP headers to pass to boto.
- debug: flag indicating whether to include debug output
- command: name of command on behalf of which this call is running.
-
- Returns:
- (base_dst_uri to use for copy, bool indicator of multi-source request).
-
- Raises:
- CommandException: if errors found.
- """
- for src_uri in src_uri_expansion:
- if src_uri.is_cloud_uri() and not src_uri.bucket_name:
- raise CommandException('Provider-only src_uri (%s)')
-
- if ContainsWildcard(dst_uri_str):
- matches = list(self.CmdWildcardIterator(dst_uri_str, headers=headers,
- debug=debug))
- if len(matches) > 1:
- raise CommandException('Destination (%s) matches more than 1 URI' %
- dst_uri_str)
- base_dst_uri = matches[0]
- else:
- base_dst_uri = self.StorageUri(dst_uri_str, debug=debug)
-
- # Make sure entire expansion didn't result in nothing to copy. This can
- # happen if user request copying a directory w/o -r option, for example.
- have_work = False
- for v in src_uri_expansion.values():
- if v:
- have_work = True
- break
- if not have_work:
- raise CommandException('Nothing to copy')
-
- # If multi-object copy request ensure base_dst_uri names a container.
- multi_src_request = (len(src_uri_expansion) > 1 or
- len(src_uri_expansion.values()[0]) > 1)
- if multi_src_request:
- self.InsistUriNamesContainer(command, base_dst_uri)
-
- # Ensure no src/dest pairs would overwrite src. Note that this is
- # more restrictive than the UNIX 'cp' command (which would, for example,
- # allow "mv * dir" and just skip the implied mv dir dir). We disallow such
- # partial completion operations in cloud copies because they are risky.
- for src_uri in iter(src_uri_expansion):
- for exp_src_uri in src_uri_expansion[src_uri]:
- new_dst_uri = self.ConstructDstUri(src_uri, exp_src_uri, base_dst_uri)
- if self.SrcDstSame(exp_src_uri, new_dst_uri):
- raise CommandException('cp: "%s" and "%s" are the same object - '
- 'abort.' % (exp_src_uri.uri, new_dst_uri.uri))
-
- return (base_dst_uri, multi_src_request)
-
- def HandleMultiSrcCopyRequst(self, src_uri_expansion, dst_uri):
- """
- Rewrites dst_uri and creates dest dir as needed, if this is a
- multi-source copy.
-
- Args:
- src_uri_expansion: result from ExpandWildcardsAndContainers call.
- dst_uri: uri constructed by ErrorCheckCopyRequest() call.
-
- Returns:
- dst_uri to use for copy.
- """
- # If src_uri and dst_uri both name containers, handle
- # two cases to make copy command work like UNIX "cp -r" works:
- # a) if dst_uri names a non-existent directory, copy objects to a new
- # directory with the dst_uri name. In this case,
- # gsutil gs://bucket/a dir
- # should create dir/a.
- # b) if dst_uri names an existing directory, copy objects under that
- # directory. In this case,
- # gsutil gs://bucket/a dir
- # should create dir/bucket/a.
- src_uri_to_check = src_uri_expansion.keys()[0]
- if (src_uri_to_check.names_container() and dst_uri.names_container() and
- os.path.exists(dst_uri.object_name)):
- new_name = ('%s%s%s' % (dst_uri.object_name, os.sep,
- src_uri_to_check.bucket_name)).rstrip('/')
- dst_uri = dst_uri.clone_replace_name(new_name)
- # Create dest directory if needed.
- if dst_uri.is_file_uri() and not os.path.exists(dst_uri.object_name):
- os.makedirs(dst_uri.object_name)
- return dst_uri
-
- def SrcDstSame(self, src_uri, dst_uri):
- """Checks if src_uri and dst_uri represent same object.
-
- We don't handle anything about hard or symbolic links.
-
- Args:
- src_uri: source StorageUri.
- dst_uri: dest StorageUri.
-
- Returns:
- Bool indication.
- """
- if src_uri.is_file_uri() and dst_uri.is_file_uri():
- # Translate a/b/./c to a/b/c, so src=dst comparison below works.
- new_src_path = re.sub('%s+\.%s+' % (os.sep, os.sep), os.sep,
- src_uri.object_name)
- new_src_path = re.sub('^.%s+' % os.sep, '', new_src_path)
- new_dst_path = re.sub('%s+\.%s+' % (os.sep, os.sep), os.sep,
- dst_uri.object_name)
- new_dst_path = re.sub('^.%s+' % os.sep, '', new_dst_path)
- return (src_uri.clone_replace_name(new_src_path).uri ==
- dst_uri.clone_replace_name(new_dst_path).uri)
- else:
- return src_uri.uri == dst_uri.uri
-
- def ConstructDstUri(self, src_uri, exp_src_uri, base_dst_uri):
- """Constructs a destination URI for CopyObjsCommand.
-
- Args:
- src_uri: src_uri to be copied.
- exp_src_uri: single URI from wildcard expansion of src_uri.
- base_dst_uri: uri constructed by ErrorCheckCopyRequest() call.
-
- Returns:
- dst_uri to use for copy.
- """
- if base_dst_uri.names_container():
- # To match naming semantics of UNIX 'cp' command, copying files
- # to buckets/dirs should result in objects/files named by just the
- # final filename component; while copying directories should result
- # in objects/files mirroring the directory hierarchy. Example of the
- # first case:
- # gsutil cp dir1/file1 gs://bucket
- # should create object gs://bucket/file1
- # Example of the second case:
- # gsutil cp dir1/dir2 gs://bucket
- # should create object gs://bucket/dir2/file2 (assuming dir1/dir2
- # contains file2).
- if src_uri.names_container():
- dst_path_start = (src_uri.object_name.rstrip(os.sep)
- .rpartition(os.sep)[-1])
- start_pos = exp_src_uri.object_name.find(dst_path_start)
- dst_key_name = exp_src_uri.object_name[start_pos:]
- else:
- # src is a file or object, so use final component of src name.
- dst_key_name = os.path.basename(exp_src_uri.object_name)
- if base_dst_uri.is_file_uri():
- # dst names a directory, so append src obj name to dst obj name.
- dst_key_name = '%s%s%s' % (base_dst_uri.object_name, os.sep,
- dst_key_name)
- self.CheckForDirFileConflict(exp_src_uri, dst_key_name)
- else:
- # dest is an object or file: use dst obj name
- dst_key_name = base_dst_uri.object_name
- return base_dst_uri.clone_replace_name(dst_key_name)
-
- def CopyObjsCommand(self, args, sub_opts=None, headers=None, debug=0,
- command='cp'):
- """Implementation of cp command.
-
- Args:
- args: command-line argument list.
- sub_opts: list of command-specific options from getopt.
- headers: dictionary containing optional HTTP headers to pass to boto.
- debug: debug level to pass in to boto connection (range 0..3).
- command: name of command on behalf of which this call is running.
-
- Raises:
- CommandException: if errors encountered.
- """
- # Expand wildcards and containers in source StorageUris.
- src_uri_expansion = self.ExpandWildcardsAndContainers(
- args[0:len(args)-1], sub_opts, headers, debug)
-
- # Check for various problems and determine base_dst_uri based for request.
- (base_dst_uri, multi_src_request) = self.ErrorCheckCopyRequest(
- src_uri_expansion, args[-1], headers, debug, command)
- # Rewrite base_dst_uri and create dest dir as needed for multi-source copy.
- if multi_src_request:
- base_dst_uri = self.HandleMultiSrcCopyRequst(src_uri_expansion,
- base_dst_uri)
-
- # Now iterate over expanded src URIs, and perform copy operations.
- total_elapsed_time = total_bytes_transferred = 0
- for src_uri in iter(src_uri_expansion):
- for exp_src_uri in src_uri_expansion[src_uri]:
- print 'Copying %s...' % exp_src_uri
- dst_uri = self.ConstructDstUri(src_uri, exp_src_uri, base_dst_uri)
- (elapsed_time, bytes_transferred) = self.PerformCopy(
- exp_src_uri, dst_uri, sub_opts, headers, debug)
- total_elapsed_time += elapsed_time
- total_bytes_transferred += bytes_transferred
- if debug == 3:
- # Note that this only counts the actual GET and PUT bytes for the copy
- # - not any transfers for doing wildcard expansion, the initial HEAD
- # request boto performs when doing a bucket.get_key() operation, etc.
- if total_bytes_transferred != 0:
- print 'Total bytes copied=%d, total elapsed time=%5.3f secs (%sps)' % (
- total_bytes_transferred, total_elapsed_time,
- MakeHumanReadable(float(total_bytes_transferred) /
- float(total_elapsed_time)))
-
- def HelpCommand(self, unused_args, unused_sub_opts=None, unused_headers=None,
- unused_debug=None):
- """Implementation of help command.
-
- Args:
- unused_args: command-line argument list.
- unused_sub_opts: list of command-specific options from getopt.
- unused_headers: dictionary containing optional HTTP headers to send.
- unused_debug: flag indicating whether to include debug output.
- """
- self.OutputUsageAndExit()
-
- def VerCommand(self, unused_args, unused_sub_opts=None, unused_headers=None,
- unused_debug=None):
- """Implementation of ver command.
-
- Args:
- unused_args: command-line argument list.
- unused_sub_opts: list of command-specific options from getopt.
- unused_headers: dictionary containing optional HTTP headers to send.
- unused_debug: flag indicating whether to include debug output.
- """
- config_ver = ''
- for path in BotoConfigLocations:
- try:
- f = open(path, 'r')
- while True:
- line = f.readline()
- if not line:
- break
- if line.find('was created by gsutil version') != -1:
- config_ver = ', config file version %s' % line.split('"')[-2]
- break
- # Only look at first first config file found in BotoConfigLocations.
- break
- except IOError:
- pass
-
- print 'gsutil version %s%s, python version %s' % (
- self.LoadVersionString(), config_ver, sys.version)
-
- def PrintBucketInfo(self, bucket_uri, listing_style, headers=None, debug=0):
- """Print listing info for given bucket.
-
- Args:
- bucket_uri: StorageUri being listed.
- listing_style: ListingStyle enum describing type of output desired.
- headers: dictionary containing optional HTTP headers to pass to boto.
- debug: debug level to pass in to boto connection (range 0..3).
-
- Returns:
- Tuple (total objects, total bytes) in the bucket.
- """
- bucket_objs = 0
- bucket_bytes = 0
- if listing_style == ListingStyle.SHORT:
- print bucket_uri
- else:
- try:
- for obj in self.CmdWildcardIterator(
- bucket_uri.clone_replace_name('*'),
- ResultType.KEYS, headers=headers, debug=debug):
- bucket_objs += 1
- bucket_bytes += obj.size
- except WildcardException, e:
- # Ignore non-matching wildcards, to allow empty bucket listings.
- if e.reason.find('No matches') == -1:
- raise e
- if listing_style == ListingStyle.LONG:
- print '%s : %s objects, %s' % (
- bucket_uri, bucket_objs, MakeHumanReadable(bucket_bytes))
- else: # listing_style == ListingStyle.LONG_LONG:
- location_constraint = bucket_uri.get_location(validate=False,
- headers=headers)
- location_output = ''
- if location_constraint:
- location_output = '\n\tLocationConstraint: %s' % location_constraint
- self.proj_id_handler.FillInProjectHeaderIfNeeded(
- 'get_acl', bucket_uri, headers)
- print '%s :\n\t%d objects, %s%s\n\tACL: %s' % (
- bucket_uri, bucket_objs, MakeHumanReadable(bucket_bytes),
- location_output, bucket_uri.get_acl(False, headers))
- return (bucket_objs, bucket_bytes)
-
- def PrintObjectInfo(self, iterated_uri, obj, listing_style, headers, debug):
- """Print listing info for given object.
-
- Args:
- iterated_uri: base StorageUri being listed (e.g., gs://abc/*).
- obj: object to be listed (or None if no associated object).
- listing_style: ListingStyle enum describing type of output desired.
- headers: dictionary containing optional HTTP headers to pass to boto.
- debug: flag indicating whether to include debug output
-
- Returns:
- Object length (if listing_style is one of the long listing formats).
-
- Raises:
- Exception: if calling bug encountered.
- """
- if listing_style == ListingStyle.SHORT:
- print UriStrFor(iterated_uri, obj)
- return 0
- elif listing_style == ListingStyle.LONG:
- # Exclude timestamp fractional secs (example: 2010-08-23T12:46:54.187Z).
- timestamp = obj.last_modified[:19].decode('utf8').encode('ascii')
- print '%10s %s %s' % (obj.size, timestamp, UriStrFor(iterated_uri, obj))
- return obj.size
- elif listing_style == ListingStyle.LONG_LONG:
- uri_str = UriStrFor(iterated_uri, obj)
- print '%s:' % uri_str
- obj.open_read()
- print '\tObject size:\t%s' % obj.size
- print '\tLast mod:\t%s' % obj.last_modified
- if obj.cache_control:
- print '\tCache control:\t%s' % obj.cache_control
- print '\tMIME type:\t%s' % obj.content_type
- if obj.content_encoding:
- print '\tContent-Encoding:\t%s' % obj.content_encoding
- if obj.metadata:
- for name in obj.metadata:
- print '\tMetadata:\t%s = %s' % (name, obj.metadata[name])
- print '\tEtag:\t%s' % obj.etag.strip('"\'')
- print '\tACL:\t%s' % (
- self.StorageUri(uri_str, debug=debug).get_acl(False, headers))
- return obj.size
- else:
- raise Exception('Unexpected ListingStyle(%s)' % listing_style)
-
- def ListCommand(self, args, sub_opts=None, headers=None, debug=0):
- """Implementation of ls command.
-
- Args:
- args: command-line argument list.
- sub_opts: list of command-specific options from getopt.
- headers: dictionary containing optional HTTP headers to pass to boto.
- debug: debug level to pass in to boto connection (range 0..3).
- """
- listing_style = ListingStyle.SHORT
- get_bucket_info = False
- if sub_opts:
- for o, a in sub_opts:
- if o == '-b':
- get_bucket_info = True
- elif o == '-l':
- listing_style = ListingStyle.LONG
- elif o == '-L':
- listing_style = ListingStyle.LONG_LONG
- elif o == '-p':
- self.proj_id_handler.SetProjectId(a)
-
- if not args:
- # default to listing all gs buckets
- args = ['gs://']
-
- total_objs = 0
- total_bytes = 0
- for uri_str in args:
- uri = self.StorageUri(uri_str, debug=debug, validate=False)
- self.proj_id_handler.FillInProjectHeaderIfNeeded('ls', uri, headers)
-
- if not uri.bucket_name:
- # Provider URI: add bucket wildcard to list buckets.
- for uri in self.CmdWildcardIterator('%s://*' % uri.scheme,
- headers=headers, debug=debug):
- (bucket_objs, bucket_bytes) = self.PrintBucketInfo(uri, listing_style,
- headers=headers,
- debug=debug)
- total_bytes += bucket_bytes
- total_objs += bucket_objs
-
- elif not uri.object_name:
- if get_bucket_info:
- # ls -b request on provider+bucket URI: List info about bucket(s).
- for uri in self.CmdWildcardIterator(uri, headers=headers,
- debug=debug):
- (bucket_objs, bucket_bytes) = self.PrintBucketInfo(uri,
- listing_style,
- headers=headers,
- debug=debug)
- total_bytes += bucket_bytes
- total_objs += bucket_objs
- else:
- # ls request on provider+bucket URI: List objects in the bucket(s).
- for obj in self.CmdWildcardIterator(uri.clone_replace_name('*'),
- ResultType.KEYS, headers=headers,
- debug=debug):
- total_bytes += self.PrintObjectInfo(uri, obj, listing_style,
- headers=headers, debug=debug)
- total_objs += 1
-
- else:
- # Provider+bucket+object URI -> list the object(s).
- for obj in self.CmdWildcardIterator(uri, ResultType.KEYS,
- headers=headers, debug=debug):
- total_bytes += self.PrintObjectInfo(uri, obj, listing_style,
- headers=headers, debug=debug)
- total_objs += 1
- if listing_style != ListingStyle.SHORT:
- print ('TOTAL: %d objects, %d bytes (%s)' %
- (total_objs, total_bytes, MakeHumanReadable(float(total_bytes))))
-
- def MakeBucketsCommand(self, args, sub_opts=None, headers=None, debug=0):
- """Implementation of mb command.
-
- Args:
- args: command-line argument list.
- sub_opts: list of command-specific options from getopt.
- headers: dictionary containing optional HTTP headers to pass to boto.
- debug: debug level to pass in to boto connection (range 0..3).
-
- Raises:
- CommandException: if errors encountered.
- """
- location = ''
- if sub_opts:
- for o, a in sub_opts:
- if o == '-l':
- location = a
- elif o == '-p':
- self.proj_id_handler.SetProjectId(a)
-
- if not headers:
- headers = {}
- else:
- headers = headers.copy()
-
- for bucket_uri_str in args:
- bucket_uri = self.StorageUri(bucket_uri_str, debug=debug)
- self.proj_id_handler.FillInProjectHeaderIfNeeded('mb', bucket_uri, headers)
- print 'Creating %s...' % bucket_uri
- bucket_uri.create_bucket(headers=headers, location=location)
-
- def MoveObjsCommand(self, args, sub_opts=None, headers=None, debug=0):
- """Implementation of mv command.
-
- Note that there is no atomic rename operation - this command is simply
- a shorthand for 'cp' followed by 'rm'.
-
- Args:
- args: command-line argument list.
- sub_opts: list of command-specific options from getopt.
- headers: dictionary containing optional HTTP headers to pass to boto.
- debug: debug level to pass in to boto connection (range 0..3).
-
- Raises:
- CommandException: if errors encountered.
- """
- # Refuse to delete a bucket or directory src URI (force users to explicitly
- # do that as a separate operation).
- src_uri_to_check = self.StorageUri(args[0], debug=debug, validate=False)
- if src_uri_to_check.names_container():
- raise CommandException('Will not remove source buckets or directories. '
- 'You must separately copy and remove for that '
- 'purpose.')
-
- if len(args) > 2:
- self.InsistUriNamesContainer('mv', self.StorageUri(args[-1]))
-
- # Expand wildcards before calling CopyObjsCommand and RemoveObjsCommand,
- # to prevent the following problem: starting with a bucket containing
- # only the object gs://bucket/obj, say the user does:
- # gsutil mv gs://bucket/* gs://bucket/d.txt
- # If we didn't expand the wildcard first, the CopyObjsCommand would
- # first copy gs://bucket/obj to gs://bucket/d.txt, and the
- # RemoveObjsCommand would then remove that object.
- exp_arg_list = []
- for uri_str in args:
- uri = self.StorageUri(uri_str, debug=debug, validate=False)
- if ContainsWildcard(uri_str):
- exp_arg_list.extend(str(u) for u in list(
- self.CmdWildcardIterator(uri, headers=headers, debug=debug)))
- else:
- exp_arg_list.append(uri.uri)
-
- self.CopyObjsCommand(exp_arg_list, sub_opts, headers, debug, 'mv')
- self.RemoveObjsCommand(exp_arg_list[0:-1], sub_opts, headers, debug)
-
- def RemoveBucketsCommand(self, args, unused_sub_opts=None, headers=None,
- debug=0):
- """Implementation of rb command.
-
- Args:
- args: command-line argument list.
- unused_sub_opts: list of command-specific options from getopt.
- headers: dictionary containing optional HTTP headers to pass to boto.
- debug: debug level to pass in to boto connection (range 0..3).
-
- Raises:
- CommandException: if errors encountered.
- """
- # Expand bucket name wildcards, if any.
- for uri_str in args:
- for uri in self.CmdWildcardIterator(uri_str, headers=headers,
- debug=debug):
- if uri.object_name:
- raise CommandException('"rb" command requires a URI with no object '
- 'name')
- print 'Removing %s...' % uri
- uri.delete_bucket(headers)
-
- def RemoveObjsCommand(self, args, sub_opts=None, headers=None, debug=0):
- """Implementation of rm command.
-
- Args:
- args: command-line argument list.
- sub_opts: list of command-specific options from getopt.
- headers: dictionary containing optional HTTP headers to pass to boto.
- debug: debug level to pass in to boto connection (range 0..3).
-
- Raises:
- CommandException: if errors encountered.
- """
- continue_on_error = False
- if sub_opts:
- for o, unused_a in sub_opts:
- if o == '-f':
- continue_on_error = True
- # Expand object name wildcards, if any.
- for uri_str in args:
- try:
- for uri in self.CmdWildcardIterator(uri_str, headers=headers,
- debug=debug):
- if uri.names_container():
- if uri.is_cloud_uri():
- # Before offering advice about how to do rm + rb, ensure those
- # commands won't fail because of bucket naming problems.
- boto.s3.connection.check_lowercase_bucketname(uri.bucket_name)
- uri_str = uri_str.rstrip('/\\')
- raise CommandException('"rm" command will not remove buckets. To '
- 'delete this/these bucket(s) do:\n\tgsutil rm '
- '%s/*\n\tgsutil rb %s' % (uri_str, uri_str))
- print 'Removing %s...' % uri
- uri.delete_key(validate=False, headers=headers)
- except Exception, e:
- if not continue_on_error:
- raise
-
- def WriteBotoConfigFile(self, config_file, use_oauth2=True,
- launch_browser=True, oauth2_scopes=[SCOPE_FULL_CONTROL]):
- """Creates a boto config file interactively.
-
- Needed credentials are obtained interactively, either by asking the user for
- access key and secret, or by walking the user through the OAuth2 approval
- flow.
-
- Args:
- config_file: file object to which the resulting config file will be
- written.
- use_oauth2: if True, walk user through OAuth2 approval flow and produce a
- config with an oauth2_refresh_token credential. If false, ask the
- user for access key and secret.
- launch_browser: in the OAuth2 approval flow, attempt to open a browser
- window and navigate to the approval URL.
- oauth2_scopes: a list of OAuth2 scopes to request authorization for, when
- using OAuth2.
- """
-
- # Collect credentials
- provider_map = {'aws': 'aws', 'google': 'gs'}
- uri_map = {'aws': 's3', 'google': 'gs'}
- key_ids = {}
- sec_keys = {}
- if use_oauth2:
- oauth2_refresh_token = oauth2_helper.OAuth2ApprovalFlow(
- oauth2_helper.OAuth2ClientFromBotoConfig(boto.config),
- oauth2_scopes, launch_browser)
- else:
- got_creds = False
- for provider in provider_map:
- if provider == 'google':
- key_ids[provider] = raw_input('What is your %s access key ID? ' %
- provider)
- sec_keys[provider] = raw_input('What is your %s secret access key? ' %
- provider)
- got_creds = True
- if not key_ids[provider] or not sec_keys[provider]:
- raise CommandException(
- 'Incomplete credentials provided. Please try again.')
- if not got_creds:
- raise CommandException('No credentials provided. Please try again.')
-
- # Write the config file prelude.
- config_file.write(CONFIG_PRELUDE_CONTENT)
- config_file.write(
- '# This file was created by gsutil version "%s"\n# at %s.\n'
- % (self.LoadVersionString(),
- datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')))
- config_file.write('#\n# You can create additional configuration files by '
- 'running\n# gsutil config [options] [-o <config-file>]\n\n\n')
-
- # Write the config file Credentials section.
- config_file.write('[Credentials]\n\n')
- if use_oauth2:
- config_file.write('# Google OAuth2 credentials (for "gs://" URIs):\n')
- config_file.write('# The following OAuth2 token is authorized for '
- 'scope(s):\n')
- for scope in oauth2_scopes:
- config_file.write('# %s\n' % scope)
- config_file.write('gs_oauth2_refresh_token = %s\n\n' %
- oauth2_refresh_token.refresh_token)
- else:
- config_file.write('# To add Google OAuth2 credentials ("gs://" URIs), '
- 'edit and uncomment the\n# following line:\n'
- '#gs_oauth2_refresh_token = <your OAuth2 refresh token>\n\n')
-
- for provider in provider_map:
- key_prefix = provider_map[provider]
- uri_scheme = uri_map[provider]
- if provider in key_ids and provider in sec_keys:
- config_file.write('# %s credentials ("%s://" URIs):\n' %
- (provider, uri_scheme))
- config_file.write('%s_access_key_id = %s\n' %
- (key_prefix, key_ids[provider]))
- config_file.write('%s_secret_access_key = %s\n' %
- (key_prefix, sec_keys[provider]))
- else:
- config_file.write('# To add %s credentials ("%s://" URIs), edit and '
- 'uncomment the\n# following two lines:\n'
- '#%s_access_key_id = <your %s access key ID>\n'
- '#%s_secret_access_key = <your %s secret access key>\n' %
- (provider, uri_scheme, key_prefix, provider, key_prefix,
- provider))
- host_key = Provider.HostKeyMap[provider]
- config_file.write('# The ability to specify an alternate storage host '
- 'is primarily for cloud\n# storage service developers.\n'
- '#%s_host = <alternate storage host address>\n\n' % host_key)
-
- # Write the config file Boto section.
- config_file.write('%s\n' % CONFIG_BOTO_SECTION_CONTENT)
-
- # Write the config file GSUtil section that doesn't depend on user input.
- config_file.write(CONFIG_INPUTLESS_GSUTIL_SECTION_CONTENT)
-
- # Write the config file GSUtil section that includes the default
- # project ID input from the user.
- if launch_browser:
- sys.stdout.write(
- 'Attempting to launch a browser to open the Google API console at '
- 'URL: %s\n\n'
- '[Note: due to a Python bug, you may see a spurious error message '
- '"object is not\n callable [...] in [...] Popen.__del__" which can '
- 'be ignored.]\n\n' % GOOG_API_CONSOLE_URI)
- sys.stdout.write(
- 'In your browser you should see the API Console. Click "Storage" and look '
- 'for the value under "Identifying your project\n\n')
- if not webbrowser.open(GOOG_API_CONSOLE_URI, new=1, autoraise=True):
- sys.stdout.write(
- 'Launching browser appears to have failed; please navigate a browser '
- 'to the following URL:\n%s\n' % GOOG_API_CONSOLE_URI)
- # Short delay; webbrowser.open on linux insists on printing out a message
- # which we don't want to run into the prompt for the auth code.
- time.sleep(2)
- else:
- sys.stdout.write(
- '\nPlease navigate your browser to %s,\nthen click "Services" on the '
- 'left side panel and ensure you have Storage'
- '\nactivated, then click "Storage" on the left side panel and find '
- 'the "x-goog-project-id" on that page.\n' %
- GOOG_API_CONSOLE_URI)
- default_project_id = raw_input('What is your project-id? ')
- project_id_section_prelude = """
-# 'default_project_id' specifies the default Google Storage project ID to use
-# with the 'mb' and 'ls' commands. If defined it overrides the default value
-# you set in the API Console. Either of these defaults can be overridden
-# by specifying the -p option to the 'mb' and 'ls' commands.
-"""
- if default_project_id:
- config_file.write('%sdefault_project_id = %s\n\n\n' %
- (project_id_section_prelude, default_project_id))
- else:
- sys.stderr.write('No default project ID entered. You will need to edit '
- 'the default_project_id value\nin your boto config file '
- 'before using "gsutil ls gs://" or "mb" commands'
- 'with the default API version 2.\n')
- config_file.write('%s#default_project_id = <value>\n\n\n' %
- project_id_section_prelude)
-
- # Write the config file OAuth2 section.
- config_file.write(CONFIG_OAUTH2_CONFIG_CONTENT)
-
- def CreateConfigCommand(self, args_unused=[], sub_opts=[],
- headers_unused=None, debug=0):
- """Implementation of the 'config' command.
-
- Args:
- sub_opts: list of command-specific options from getopt.
- debug: debug level to pass in to boto connection (range 0..3).
-
- Raises:
- CommandException: if errors encountered.
- """
- scopes = []
- use_oauth2 = True
- launch_browser = False
- output_file_name = None
- for opt, opt_arg in sub_opts:
- if opt == '-h':
- sys.stderr.write(CONFIG_COMMAND_HELP)
- sys.exit(0)
- if opt == '-a':
- use_oauth2 = False
- if opt == '-b':
- launch_browser = True
- if opt == '-f':
- scopes.append(SCOPE_FULL_CONTROL)
- if opt == '-w':
- scopes.append(SCOPE_READ_WRITE)
- if opt == '-r':
- scopes.append(SCOPE_READ_ONLY)
- if opt == '-s':
- scopes.append(opt_arg)
- if opt == '-o':
- output_file_name = opt_arg
-
- if use_oauth2 and not _HAVE_OAUTH2:
- raise CommandException(
- "OAuth2 is only supported when running under Python 2.6 or later\n"
- "(unless additional dependencies are installed, "
- "see README for details);\n"
- "you are running Python %s.\nUse 'gsutil config -a' to create a "
- "config with Developer Key authentication credentials." % sys.version)
-
- if len(scopes) == 0:
- scopes.append(SCOPE_FULL_CONTROL)
-
- if output_file_name is None:
- # Use the default config file name, if it doesn't exist or can be moved
- # out of the way without clobbering an existing backup file.
- default_config_path = os.path.expanduser(os.path.join('~', '.boto'))
- if not os.path.exists(default_config_path):
- output_file_name = default_config_path
- else:
- default_config_path_bak = default_config_path + ".bak"
- if os.path.exists(default_config_path_bak):
- raise CommandException("Cannot back up existing config "
- "file '%s': backup file exists ('%s')."
- % (default_config_path, default_config_path_bak))
- else:
- try:
- sys.stderr.write(
- "Backing up existing config file '%s' to '%s'...\n"
- % (default_config_path, default_config_path_bak))
- os.rename(default_config_path, default_config_path_bak)
- except e:
- raise CommandException("Failed to back up existing config "
- "file ('%s' -> '%s'): %s."
- % (default_config_path, default_config_path_bak, e))
- output_file_name = default_config_path
-
- if output_file_name == '-':
- output_file = sys.stdout
- else:
- output_file = OpenConfigFile(output_file_name)
- sys.stderr.write(
- 'This script will create a boto config file at\n%s\ncontaining your '
- 'credentials, based on your responses to the following questions.\n\n'
- % output_file_name)
-
- try:
- self.WriteBotoConfigFile(output_file, use_oauth2=use_oauth2,
- launch_browser=launch_browser, oauth2_scopes=scopes)
- except Exception, e:
- # If an error occurred during config file creation, remove the invalid
- # config file.
- if output_file_name != '-':
- output_file.close()
- os.unlink(output_file_name)
- raise
-
- if output_file_name != '-':
- output_file.close()
- sys.stderr.write(
- '\nBoto config file "%s" created. If you need to use\na proxy to '
- 'access the Internet please see the instructions in that file.\n'
- % output_file_name)
« no previous file with comments | « third_party/gsutil/20110627/gslib/__init__.py ('k') | third_party/gsutil/20110627/gslib/exception.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698