| OLD | NEW |
| 1 #!/usr/bin/env python | 1 # -*- coding: utf-8 -*- |
| 2 # coding=utf8 | |
| 3 # Copyright 2011 Google Inc. All Rights Reserved. | 2 # Copyright 2011 Google Inc. All Rights Reserved. |
| 4 # | 3 # |
| 5 # Licensed under the Apache License, Version 2.0 (the "License"); | 4 # Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 # you may not use this file except in compliance with the License. | 5 # you may not use this file except in compliance with the License. |
| 7 # You may obtain a copy of the License at | 6 # You may obtain a copy of the License at |
| 8 # | 7 # |
| 9 # http://www.apache.org/licenses/LICENSE-2.0 | 8 # http://www.apache.org/licenses/LICENSE-2.0 |
| 10 # | 9 # |
| 11 # Unless required by applicable law or agreed to in writing, software | 10 # Unless required by applicable law or agreed to in writing, software |
| 12 # distributed under the License is distributed on an "AS IS" BASIS, | 11 # distributed under the License is distributed on an "AS IS" BASIS, |
| 13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 # See the License for the specific language governing permissions and | 13 # See the License for the specific language governing permissions and |
| 15 # limitations under the License. | 14 # limitations under the License. |
| 16 | |
| 17 """Class that runs a named gsutil command.""" | 15 """Class that runs a named gsutil command.""" |
| 18 | 16 |
| 19 import boto | 17 from __future__ import absolute_import |
| 18 |
| 20 import difflib | 19 import difflib |
| 21 import logging | 20 import logging |
| 21 import os |
| 22 import pkgutil | 22 import pkgutil |
| 23 import os | |
| 24 import sys | 23 import sys |
| 25 import textwrap | 24 import textwrap |
| 26 import time | 25 import time |
| 27 | 26 |
| 27 import boto |
| 28 from boto.storage_uri import BucketStorageUri | 28 from boto.storage_uri import BucketStorageUri |
| 29 import gslib | 29 import gslib |
| 30 import gslib.commands | |
| 31 from gslib.command import Command | 30 from gslib.command import Command |
| 32 from gslib.command import COMMAND_NAME | 31 from gslib.command import GetFailureCount |
| 33 from gslib.command import COMMAND_NAME_ALIASES | |
| 34 from gslib.command import OLD_ALIAS_MAP | 32 from gslib.command import OLD_ALIAS_MAP |
| 35 from gslib.command import ShutDownGsutil | 33 from gslib.command import ShutDownGsutil |
| 34 import gslib.commands |
| 35 from gslib.cs_api_map import GsutilApiClassMapFactory |
| 36 from gslib.exception import CommandException | 36 from gslib.exception import CommandException |
| 37 from gslib.help_provider import SUBCOMMAND_HELP_TEXT | 37 from gslib.gcs_json_api import GcsJsonApi |
| 38 from gslib.storage_uri_builder import StorageUriBuilder | 38 from gslib.no_op_credentials import NoOpCredentials |
| 39 from gslib.util import CompareVersions | 39 from gslib.util import CompareVersions |
| 40 from gslib.util import ConfigureNoOpAuthIfNeeded | |
| 41 from gslib.util import GetGsutilVersionModifiedTime | 40 from gslib.util import GetGsutilVersionModifiedTime |
| 42 from gslib.util import GSUTIL_PUB_TARBALL | 41 from gslib.util import GSUTIL_PUB_TARBALL |
| 43 from gslib.util import IsRunningInteractively | 42 from gslib.util import IsRunningInteractively |
| 44 from gslib.util import LAST_CHECKED_FOR_GSUTIL_UPDATE_TIMESTAMP_FILE | 43 from gslib.util import LAST_CHECKED_FOR_GSUTIL_UPDATE_TIMESTAMP_FILE |
| 45 from gslib.util import LookUpGsutilVersion | 44 from gslib.util import LookUpGsutilVersion |
| 46 from gslib.util import MultiprocessingIsAvailable | 45 from gslib.util import MultiprocessingIsAvailable |
| 47 from gslib.util import RELEASE_NOTES_URL | 46 from gslib.util import RELEASE_NOTES_URL |
| 48 from gslib.util import SECONDS_PER_DAY | 47 from gslib.util import SECONDS_PER_DAY |
| 48 from gslib.util import UTF8 |
| 49 | 49 |
| 50 | 50 |
| 51 def HandleArgCoding(args): | 51 def HandleArgCoding(args): |
| 52 """ | 52 """Handles coding of command-line args. |
| 53 Handles coding of command-line args. | |
| 54 | 53 |
| 55 Args: | 54 Args: |
| 56 args: array of command-line args. | 55 args: array of command-line args. |
| 57 | 56 |
| 58 Returns: | 57 Returns: |
| 59 array of command-line args. | 58 array of command-line args. |
| 60 | 59 |
| 61 Raises: | 60 Raises: |
| 62 CommandException: if errors encountered. | 61 CommandException: if errors encountered. |
| 63 """ | 62 """ |
| 64 # Python passes arguments from the command line as byte strings. To | 63 # Python passes arguments from the command line as byte strings. To |
| 65 # correctly interpret them, we decode ones other than -h and -p args (which | 64 # correctly interpret them, we decode ones other than -h and -p args (which |
| 66 # will be passed as headers, and thus per HTTP spec should not be encoded) as | 65 # will be passed as headers, and thus per HTTP spec should not be encoded) as |
| 67 # utf-8. The exception is x-goog-meta-* headers, which are allowed to contain | 66 # utf-8. The exception is x-goog-meta-* headers, which are allowed to contain |
| 68 # non-ASCII content (and hence, should be decoded), per | 67 # non-ASCII content (and hence, should be decoded), per |
| 69 # https://developers.google.com/storage/docs/gsutil/addlhelp/WorkingWithObject
Metadata | 68 # https://developers.google.com/storage/docs/gsutil/addlhelp/WorkingWithObject
Metadata |
| 70 processing_header = False | 69 processing_header = False |
| 71 for i in range(len(args)): | 70 for i in range(len(args)): |
| 72 arg = args[i] | 71 arg = args[i] |
| 73 decoded = arg.decode('utf-8') | 72 decoded = arg.decode(UTF8) |
| 74 if processing_header: | 73 if processing_header: |
| 75 if arg.lower().startswith('x-goog-meta'): | 74 if arg.lower().startswith('x-goog-meta'): |
| 76 args[i] = decoded | 75 args[i] = decoded |
| 77 else: | 76 else: |
| 78 try: | 77 try: |
| 79 # Try to encode as ASCII to check for invalid header values (which | 78 # Try to encode as ASCII to check for invalid header values (which |
| 80 # can't be sent over HTTP). | 79 # can't be sent over HTTP). |
| 81 decoded.encode('ascii') | 80 decoded.encode('ascii') |
| 82 except UnicodeEncodeError: | 81 except UnicodeEncodeError: |
| 83 # Raise the CommandException using the decoded value because | 82 # Raise the CommandException using the decoded value because |
| 84 # _OutputAndExit function re-encodes at the end. | 83 # _OutputAndExit function re-encodes at the end. |
| 85 raise CommandException( | 84 raise CommandException( |
| 86 'Invalid non-ASCII header value (%s).\nOnly ASCII characters are ' | 85 'Invalid non-ASCII header value (%s).\nOnly ASCII characters are ' |
| 87 'allowed in headers other than x-goog-meta- headers' % decoded) | 86 'allowed in headers other than x-goog-meta- headers' % decoded) |
| 88 else: | 87 else: |
| 89 args[i] = decoded | 88 args[i] = decoded |
| 90 processing_header = (arg in ('-h', '-p')) | 89 processing_header = (arg in ('-h', '-p')) |
| 91 return args | 90 return args |
| 92 | 91 |
| 93 | 92 |
| 94 class CommandRunner(object): | 93 class CommandRunner(object): |
| 94 """Runs gsutil commands and does some top-level argument handling.""" |
| 95 | 95 |
| 96 def __init__(self, config_file_list, | 96 def __init__(self, bucket_storage_uri_class=BucketStorageUri, |
| 97 bucket_storage_uri_class=BucketStorageUri): | 97 gsutil_api_class_map_factory=GsutilApiClassMapFactory): |
| 98 """ | 98 """Instantiates a CommandRunner. |
| 99 |
| 99 Args: | 100 Args: |
| 100 config_file_list: Config file list returned by GetBotoConfigFileList(). | |
| 101 bucket_storage_uri_class: Class to instantiate for cloud StorageUris. | 101 bucket_storage_uri_class: Class to instantiate for cloud StorageUris. |
| 102 Settable for testing/mocking. | 102 Settable for testing/mocking. |
| 103 gsutil_api_class_map_factory: Creates map of cloud storage interfaces. |
| 104 Settable for testing/mocking. |
| 103 """ | 105 """ |
| 104 self.config_file_list = config_file_list | |
| 105 self.bucket_storage_uri_class = bucket_storage_uri_class | 106 self.bucket_storage_uri_class = bucket_storage_uri_class |
| 107 self.gsutil_api_class_map_factory = gsutil_api_class_map_factory |
| 106 self.command_map = self._LoadCommandMap() | 108 self.command_map = self._LoadCommandMap() |
| 107 | 109 |
| 108 def _LoadCommandMap(self): | 110 def _LoadCommandMap(self): |
| 109 """Returns dict mapping each command_name to implementing class.""" | 111 """Returns dict mapping each command_name to implementing class.""" |
| 110 # Import all gslib.commands submodules. | 112 # Import all gslib.commands submodules. |
| 111 for _, module_name, _ in pkgutil.iter_modules(gslib.commands.__path__): | 113 for _, module_name, _ in pkgutil.iter_modules(gslib.commands.__path__): |
| 112 __import__('gslib.commands.%s' % module_name) | 114 __import__('gslib.commands.%s' % module_name) |
| 113 | 115 |
| 114 command_map = {} | 116 command_map = {} |
| 115 # Only include Command subclasses in the dict. | 117 # Only include Command subclasses in the dict. |
| 116 for command in Command.__subclasses__(): | 118 for command in Command.__subclasses__(): |
| 117 command_map[command.command_spec[COMMAND_NAME]] = command | 119 command_map[command.command_spec.command_name] = command |
| 118 for command_name_aliases in command.command_spec[COMMAND_NAME_ALIASES]: | 120 for command_name_aliases in command.command_spec.command_name_aliases: |
| 119 command_map[command_name_aliases] = command | 121 command_map[command_name_aliases] = command |
| 120 return command_map | 122 return command_map |
| 121 | 123 |
| 122 def RunNamedCommand(self, command_name, args=None, headers=None, debug=0, | 124 def RunNamedCommand(self, command_name, args=None, headers=None, debug=0, |
| 123 parallel_operations=False, test_method=None, | 125 parallel_operations=False, test_method=None, |
| 124 skip_update_check=False, logging_filters=None): | 126 skip_update_check=False, logging_filters=None, |
| 125 """Runs the named command. Used by gsutil main, commands built atop | 127 do_shutdown=True): |
| 126 other commands, and tests . | 128 """Runs the named command. |
| 127 | 129 |
| 128 Args: | 130 Used by gsutil main, commands built atop other commands, and tests. |
| 129 command_name: The name of the command being run. | |
| 130 args: Command-line args (arg0 = actual arg, not command name ala bash). | |
| 131 headers: Dictionary containing optional HTTP headers to pass to boto. | |
| 132 debug: Debug level to pass in to boto connection (range 0..3). | |
| 133 parallel_operations: Should command operations be executed in parallel? | |
| 134 test_method: Optional general purpose method for testing purposes. | |
| 135 Application and semantics of this method will vary by | |
| 136 command and test type. | |
| 137 skip_update_check: Set to True to disable checking for gsutil updates. | |
| 138 logging_filters: Optional list of logging.Filters to apply to this | |
| 139 command's logger. | |
| 140 | 131 |
| 141 Raises: | 132 Args: |
| 142 CommandException: if errors encountered. | 133 command_name: The name of the command being run. |
| 134 args: Command-line args (arg0 = actual arg, not command name ala bash). |
| 135 headers: Dictionary containing optional HTTP headers to pass to boto. |
| 136 debug: Debug level to pass in to boto connection (range 0..3). |
| 137 parallel_operations: Should command operations be executed in parallel? |
| 138 test_method: Optional general purpose method for testing purposes. |
| 139 Application and semantics of this method will vary by |
| 140 command and test type. |
| 141 skip_update_check: Set to True to disable checking for gsutil updates. |
| 142 logging_filters: Optional list of logging.Filters to apply to this |
| 143 command's logger. |
| 144 do_shutdown: Stop all parallelism framework workers iff this is True. |
| 145 |
| 146 Raises: |
| 147 CommandException: if errors encountered. |
| 148 |
| 149 Returns: |
| 150 Return value(s) from Command that was run. |
| 143 """ | 151 """ |
| 144 ConfigureNoOpAuthIfNeeded() | |
| 145 if (not skip_update_check and | 152 if (not skip_update_check and |
| 146 self._MaybeCheckForAndOfferSoftwareUpdate(command_name, debug)): | 153 self.MaybeCheckForAndOfferSoftwareUpdate(command_name, debug)): |
| 147 command_name = 'update' | 154 command_name = 'update' |
| 148 args = ['-n'] | 155 args = ['-n'] |
| 149 | 156 |
| 150 if not args: | 157 if not args: |
| 151 args = [] | 158 args = [] |
| 152 | 159 |
| 153 # Include api_version header in all commands. | 160 # Include api_version header in all commands. |
| 154 api_version = boto.config.get_value('GSUtil', 'default_api_version', '1') | 161 api_version = boto.config.get_value('GSUtil', 'default_api_version', '1') |
| 155 if not headers: | 162 if not headers: |
| 156 headers = {} | 163 headers = {} |
| 157 headers['x-goog-api-version'] = api_version | 164 headers['x-goog-api-version'] = api_version |
| 158 | 165 |
| 159 if command_name not in self.command_map: | 166 if command_name not in self.command_map: |
| 160 close_matches = difflib.get_close_matches( | 167 close_matches = difflib.get_close_matches( |
| 161 command_name, self.command_map.keys(), n=1) | 168 command_name, self.command_map.keys(), n=1) |
| 162 if len(close_matches): | 169 if close_matches: |
| 163 # Instead of suggesting a deprecated command alias, suggest the new | 170 # Instead of suggesting a deprecated command alias, suggest the new |
| 164 # name for that command. | 171 # name for that command. |
| 165 translated_command_name = ( | 172 translated_command_name = ( |
| 166 OLD_ALIAS_MAP.get(close_matches[0], close_matches)[0]) | 173 OLD_ALIAS_MAP.get(close_matches[0], close_matches)[0]) |
| 167 print >> sys.stderr, 'Did you mean this?' | 174 print >> sys.stderr, 'Did you mean this?' |
| 168 print >> sys.stderr, '\t%s' % translated_command_name | 175 print >> sys.stderr, '\t%s' % translated_command_name |
| 169 raise CommandException('Invalid command "%s".' % command_name) | 176 raise CommandException('Invalid command "%s".' % command_name) |
| 170 if '--help' in args: | 177 if '--help' in args: |
| 171 new_args = [command_name] | 178 new_args = [command_name] |
| 172 original_command_class = self.command_map[command_name] | 179 original_command_class = self.command_map[command_name] |
| 173 subcommands = original_command_class.help_spec.get( | 180 subcommands = original_command_class.help_spec.subcommand_help_text.keys() |
| 174 SUBCOMMAND_HELP_TEXT, {}).keys() | |
| 175 for arg in args: | 181 for arg in args: |
| 176 if arg in subcommands: | 182 if arg in subcommands: |
| 177 new_args.append(arg) | 183 new_args.append(arg) |
| 178 break # Take the first match and throw away the rest. | 184 break # Take the first match and throw away the rest. |
| 179 args = new_args | 185 args = new_args |
| 180 command_name = 'help' | 186 command_name = 'help' |
| 181 | 187 |
| 182 args = HandleArgCoding(args) | 188 args = HandleArgCoding(args) |
| 183 | 189 |
| 184 command_class = self.command_map[command_name] | 190 command_class = self.command_map[command_name] |
| 185 command_inst = command_class( | 191 command_inst = command_class( |
| 186 self, args, headers, debug, parallel_operations, self.config_file_list, | 192 self, args, headers, debug, parallel_operations, |
| 187 self.bucket_storage_uri_class, test_method, logging_filters, | 193 self.bucket_storage_uri_class, self.gsutil_api_class_map_factory, |
| 188 command_alias_used=command_name) | 194 test_method, logging_filters, command_alias_used=command_name) |
| 195 return_code = command_inst.RunCommand() |
| 189 | 196 |
| 190 return_values = command_inst.RunCommand() | 197 if MultiprocessingIsAvailable()[0] and do_shutdown: |
| 191 if MultiprocessingIsAvailable()[0]: | |
| 192 ShutDownGsutil() | 198 ShutDownGsutil() |
| 193 return return_values | 199 if GetFailureCount() > 0: |
| 200 return_code = 1 |
| 201 return return_code |
| 194 | 202 |
| 195 def _MaybeCheckForAndOfferSoftwareUpdate(self, command_name, debug): | 203 def MaybeCheckForAndOfferSoftwareUpdate(self, command_name, debug): |
| 196 """Checks the last time we checked for an update, and if it's been longer | 204 """Checks the last time we checked for an update and offers one if needed. |
| 197 than the configured threshold offers the user to update gsutil. | |
| 198 | 205 |
| 199 Args: | 206 Offer is made if the time since the last update check is longer |
| 200 command_name: The name of the command being run. | 207 than the configured threshold offers the user to update gsutil. |
| 201 debug: Debug level to pass in to boto connection (range 0..3). | |
| 202 | 208 |
| 203 Returns: | 209 Args: |
| 204 True if the user decides to update. | 210 command_name: The name of the command being run. |
| 211 debug: Debug level to pass in to boto connection (range 0..3). |
| 212 |
| 213 Returns: |
| 214 True if the user decides to update. |
| 205 """ | 215 """ |
| 206 # Don't try to interact with user if: | 216 # Don't try to interact with user if: |
| 207 # - gsutil is not connected to a tty (e.g., if being run from cron); | 217 # - gsutil is not connected to a tty (e.g., if being run from cron); |
| 208 # - user is running gsutil -q | 218 # - user is running gsutil -q |
| 209 # - user is running the config command (which could otherwise attempt to | 219 # - user is running the config command (which could otherwise attempt to |
| 210 # check for an update for a user running behind a proxy, who has not yet | 220 # check for an update for a user running behind a proxy, who has not yet |
| 211 # configured gsutil to go through the proxy; for such users we need the | 221 # configured gsutil to go through the proxy; for such users we need the |
| 212 # first connection attempt to be made by the gsutil config command). | 222 # first connection attempt to be made by the gsutil config command). |
| 213 # - user is running the version command (which gets run when using | 223 # - user is running the version command (which gets run when using |
| 214 # gsutil -D, which would prevent users with proxy config problems from | 224 # gsutil -D, which would prevent users with proxy config problems from |
| 215 # sending us gsutil -D output). | 225 # sending us gsutil -D output). |
| 216 # - user is running the update command (which could otherwise cause an | 226 # - user is running the update command (which could otherwise cause an |
| 217 # additional note that an update is available when user is already trying | 227 # additional note that an update is available when user is already trying |
| 218 # to perform an update); | 228 # to perform an update); |
| 219 # - user specified gs_host (which could be a non-production different | 229 # - user specified gs_host (which could be a non-production different |
| 220 # service instance, in which case credentials won't work for checking | 230 # service instance, in which case credentials won't work for checking |
| 221 # gsutil tarball). | 231 # gsutil tarball). |
| 232 logger = logging.getLogger() |
| 222 gs_host = boto.config.get('Credentials', 'gs_host', None) | 233 gs_host = boto.config.get('Credentials', 'gs_host', None) |
| 223 if (not IsRunningInteractively() | 234 if (not IsRunningInteractively() |
| 224 or command_name in ('config', 'update', 'ver', 'version') | 235 or command_name in ('config', 'update', 'ver', 'version') |
| 225 or not logging.getLogger().isEnabledFor(logging.INFO) | 236 or not logger.isEnabledFor(logging.INFO) |
| 226 or gs_host): | 237 or gs_host): |
| 227 return False | 238 return False |
| 228 | 239 |
| 229 software_update_check_period = boto.config.getint( | 240 software_update_check_period = boto.config.getint( |
| 230 'GSUtil', 'software_update_check_period', 30) | 241 'GSUtil', 'software_update_check_period', 30) |
| 231 # Setting software_update_check_period to 0 means periodic software | 242 # Setting software_update_check_period to 0 means periodic software |
| 232 # update checking is disabled. | 243 # update checking is disabled. |
| 233 if software_update_check_period == 0: | 244 if software_update_check_period == 0: |
| 234 return False | 245 return False |
| 235 | 246 |
| 236 cur_ts = int(time.time()) | 247 cur_ts = int(time.time()) |
| 237 if not os.path.isfile(LAST_CHECKED_FOR_GSUTIL_UPDATE_TIMESTAMP_FILE): | 248 if not os.path.isfile(LAST_CHECKED_FOR_GSUTIL_UPDATE_TIMESTAMP_FILE): |
| 238 # Set last_checked_ts from date of VERSION file, so if the user installed | 249 # Set last_checked_ts from date of VERSION file, so if the user installed |
| 239 # an old copy of gsutil it will get noticed (and an update offered) the | 250 # an old copy of gsutil it will get noticed (and an update offered) the |
| 240 # first time they try to run it. | 251 # first time they try to run it. |
| 241 last_checked_ts = GetGsutilVersionModifiedTime() | 252 last_checked_ts = GetGsutilVersionModifiedTime() |
| 242 with open(LAST_CHECKED_FOR_GSUTIL_UPDATE_TIMESTAMP_FILE, 'w') as f: | 253 with open(LAST_CHECKED_FOR_GSUTIL_UPDATE_TIMESTAMP_FILE, 'w') as f: |
| 243 f.write(str(last_checked_ts)) | 254 f.write(str(last_checked_ts)) |
| 244 else: | 255 else: |
| 245 try: | 256 try: |
| 246 with open(LAST_CHECKED_FOR_GSUTIL_UPDATE_TIMESTAMP_FILE, 'r') as f: | 257 with open(LAST_CHECKED_FOR_GSUTIL_UPDATE_TIMESTAMP_FILE, 'r') as f: |
| 247 last_checked_ts = int(f.readline()) | 258 last_checked_ts = int(f.readline()) |
| 248 except (TypeError, ValueError): | 259 except (TypeError, ValueError): |
| 249 return False | 260 return False |
| 250 | 261 |
| 251 if (cur_ts - last_checked_ts | 262 if (cur_ts - last_checked_ts |
| 252 > software_update_check_period * SECONDS_PER_DAY): | 263 > software_update_check_period * SECONDS_PER_DAY): |
| 253 suri_builder = StorageUriBuilder(debug, self.bucket_storage_uri_class) | 264 # Create a credential-less gsutil API to check for the public |
| 254 cur_ver = LookUpGsutilVersion(suri_builder.StorageUri(GSUTIL_PUB_TARBALL)) | 265 # update tarball. |
| 266 gsutil_api = GcsJsonApi(self.bucket_storage_uri_class, logger, |
| 267 credentials=NoOpCredentials(), debug=debug) |
| 268 |
| 269 cur_ver = LookUpGsutilVersion(gsutil_api, GSUTIL_PUB_TARBALL) |
| 255 with open(LAST_CHECKED_FOR_GSUTIL_UPDATE_TIMESTAMP_FILE, 'w') as f: | 270 with open(LAST_CHECKED_FOR_GSUTIL_UPDATE_TIMESTAMP_FILE, 'w') as f: |
| 256 f.write(str(cur_ts)) | 271 f.write(str(cur_ts)) |
| 257 (g, m) = CompareVersions(cur_ver, gslib.VERSION) | 272 (g, m) = CompareVersions(cur_ver, gslib.VERSION) |
| 258 if m: | 273 if m: |
| 259 print '\n'.join(textwrap.wrap( | 274 print '\n'.join(textwrap.wrap( |
| 260 'A newer version of gsutil (%s) is available than the version you ' | 275 'A newer version of gsutil (%s) is available than the version you ' |
| 261 'are running (%s). NOTE: This is a major new version, so it is ' | 276 'are running (%s). NOTE: This is a major new version, so it is ' |
| 262 'strongly recommended that you review the release note details at %s
' | 277 'strongly recommended that you review the release note details at ' |
| 263 'before updating to this version, especially if you use gsutil in ' | 278 '%s before updating to this version, especially if you use gsutil ' |
| 264 'scripts.' % (cur_ver, gslib.VERSION, RELEASE_NOTES_URL))) | 279 'in scripts.' % (cur_ver, gslib.VERSION, RELEASE_NOTES_URL))) |
| 265 if gslib.IS_PACKAGE_INSTALL: | 280 if gslib.IS_PACKAGE_INSTALL: |
| 266 return False | 281 return False |
| 267 print | 282 print |
| 268 answer = raw_input('Would you like to update [y/N]? ') | 283 answer = raw_input('Would you like to update [y/N]? ') |
| 269 return answer and answer.lower()[0] == 'y' | 284 return answer and answer.lower()[0] == 'y' |
| 270 elif g: | 285 elif g: |
| 271 print '\n'.join(textwrap.wrap( | 286 print '\n'.join(textwrap.wrap( |
| 272 'A newer version of gsutil (%s) is available than the version you ' | 287 'A newer version of gsutil (%s) is available than the version you ' |
| 273 'are running (%s). A detailed log of gsutil release changes is ' | 288 'are running (%s). A detailed log of gsutil release changes is ' |
| 274 'available at %s if you would like to read them before updating.' | 289 'available at %s if you would like to read them before updating.' |
| 275 % (cur_ver, gslib.VERSION, RELEASE_NOTES_URL))) | 290 % (cur_ver, gslib.VERSION, RELEASE_NOTES_URL))) |
| 276 if gslib.IS_PACKAGE_INSTALL: | 291 if gslib.IS_PACKAGE_INSTALL: |
| 277 return False | 292 return False |
| 278 print | 293 print |
| 279 answer = raw_input('Would you like to update [Y/n]? ') | 294 answer = raw_input('Would you like to update [Y/n]? ') |
| 280 return not answer or answer.lower()[0] != 'n' | 295 return not answer or answer.lower()[0] != 'n' |
| 281 return False | 296 return False |
| OLD | NEW |