OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/env python |
| 2 # -*- coding: utf-8 -*- |
| 3 # Copyright 2013 Google Inc. All Rights Reserved. |
| 4 # |
| 5 # Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 # you may not use this file except in compliance with the License. |
| 7 # You may obtain a copy of the License at |
| 8 # |
| 9 # http://www.apache.org/licenses/LICENSE-2.0 |
| 10 # |
| 11 # Unless required by applicable law or agreed to in writing, software |
| 12 # distributed under the License is distributed on an "AS IS" BASIS, |
| 13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 # See the License for the specific language governing permissions and |
| 15 # limitations under the License. |
| 16 """Main module for Google Cloud Storage command line tool.""" |
| 17 |
| 18 from __future__ import absolute_import |
| 19 |
| 20 import ConfigParser |
| 21 import datetime |
| 22 import errno |
| 23 import getopt |
| 24 import logging |
| 25 import os |
| 26 import re |
| 27 import signal |
| 28 import socket |
| 29 import sys |
| 30 import textwrap |
| 31 import traceback |
| 32 |
| 33 # Load the gsutil version number and append it to boto.UserAgent so the value is |
| 34 # set before anything instantiates boto. This has to run after THIRD_PARTY_DIR |
| 35 # is modified (done in gsutil.py) but before any calls are made that would cause |
| 36 # boto.s3.Connection to be loaded - otherwise the Connection class would end up |
| 37 # with a static reference to the pre-modified version of the UserAgent field, |
| 38 # so boto requests would not include gsutil/version# in the UserAgent string. |
| 39 import boto |
| 40 import gslib |
| 41 # TODO: gsutil-beta: Cloud SDK scans for this string and performs |
| 42 # substitution; ensure this works with both apitools and boto. |
| 43 boto.UserAgent += ' gsutil/%s (%s)' % (gslib.VERSION, sys.platform) |
| 44 if os.environ.get('CLOUDSDK_WRAPPER') == '1': |
| 45 boto.UserAgent += ' Cloud SDK Command Line Tool' |
| 46 if os.environ.get('CLOUDSDK_VERSION'): |
| 47 boto.UserAgent += ' %s' % os.environ.get('CLOUDSDK_VERSION') |
| 48 |
| 49 # pylint: disable=g-bad-import-order |
| 50 # pylint: disable=g-import-not-at-top |
| 51 import httplib2 |
| 52 import oauth2client |
| 53 from gslib import wildcard_iterator |
| 54 from gslib.cloud_api import AccessDeniedException |
| 55 from gslib.cloud_api import ArgumentException |
| 56 from gslib.cloud_api import BadRequestException |
| 57 from gslib.cloud_api import ProjectIdException |
| 58 from gslib.cloud_api import ServiceException |
| 59 from gslib.command_runner import CommandRunner |
| 60 import gslib.exception |
| 61 from gslib.exception import CommandException |
| 62 import apitools.base.py.exceptions as apitools_exceptions |
| 63 from gslib.util import CreateLock |
| 64 from gslib.util import GetBotoConfigFileList |
| 65 from gslib.util import GetCertsFile |
| 66 from gslib.util import GetCleanupFiles |
| 67 from gslib.util import GsutilStreamHandler |
| 68 from gslib.util import ProxyInfoFromEnvironmentVar |
| 69 from gslib.sig_handling import GetCaughtSignals |
| 70 from gslib.sig_handling import InitializeSignalHandling |
| 71 from gslib.sig_handling import RegisterSignalHandler |
| 72 |
| 73 GSUTIL_CLIENT_ID = '909320924072.apps.googleusercontent.com' |
| 74 # Google OAuth2 clients always have a secret, even if the client is an installed |
| 75 # application/utility such as gsutil. Of course, in such cases the "secret" is |
| 76 # actually publicly known; security depends entirely on the secrecy of refresh |
| 77 # tokens, which effectively become bearer tokens. |
| 78 GSUTIL_CLIENT_NOTSOSECRET = 'p3RlpR10xMFh9ZXBS/ZNLYUu' |
| 79 if os.environ.get('CLOUDSDK_WRAPPER') == '1': |
| 80 # Cloud SDK installs have a separate client ID / secret. |
| 81 GSUTIL_CLIENT_ID = '32555940559.apps.googleusercontent.com' |
| 82 GSUTIL_CLIENT_NOTSOSECRET = 'ZmssLNjJy2998hD4CTg2ejr2' |
| 83 |
| 84 CONFIG_KEYS_TO_REDACT = ['proxy', 'proxy_port', 'proxy_user', 'proxy_pass'] |
| 85 |
| 86 |
| 87 # We don't use the oauth2 authentication plugin directly; importing it here |
| 88 # ensures that it's loaded and available by default when an operation requiring |
| 89 # authentication is performed. |
| 90 try: |
| 91 # pylint: disable=unused-import,g-import-not-at-top |
| 92 import gcs_oauth2_boto_plugin |
| 93 except ImportError: |
| 94 pass |
| 95 |
| 96 DEBUG_WARNING = """ |
| 97 ***************************** WARNING ***************************** |
| 98 *** You are running gsutil with debug output enabled. |
| 99 *** Be aware that debug output includes authentication credentials. |
| 100 *** Make sure to remove the value of the Authorization header for |
| 101 *** each HTTP request printed to the console prior to posting to |
| 102 *** a public medium such as a forum post or Stack Overflow. |
| 103 ***************************** WARNING ***************************** |
| 104 """.lstrip() |
| 105 |
| 106 HTTP_WARNING = """ |
| 107 ***************************** WARNING ***************************** |
| 108 *** You are running gsutil with the "https_validate_certificates" config |
| 109 *** variable set to False. This option should always be set to True in |
| 110 *** production environments to protect against man-in-the-middle attacks, |
| 111 *** and leaking of user data. |
| 112 ***************************** WARNING ***************************** |
| 113 """.lstrip() |
| 114 |
| 115 debug = 0 |
| 116 test_exception_traces = False |
| 117 |
| 118 |
| 119 # pylint: disable=unused-argument |
| 120 def _CleanupSignalHandler(signal_num, cur_stack_frame): |
| 121 """Cleans up if process is killed with SIGINT, SIGQUIT or SIGTERM.""" |
| 122 _Cleanup() |
| 123 |
| 124 |
| 125 def _Cleanup(): |
| 126 for fname in GetCleanupFiles(): |
| 127 try: |
| 128 os.unlink(fname) |
| 129 except: # pylint: disable=bare-except |
| 130 pass |
| 131 |
| 132 |
| 133 def _OutputAndExit(message): |
| 134 """Outputs message and exists with code 1.""" |
| 135 from gslib.util import UTF8 # pylint: disable=g-import-not-at-top |
| 136 if debug >= 2 or test_exception_traces: |
| 137 stack_trace = traceback.format_exc() |
| 138 err = ('DEBUG: Exception stack trace:\n %s\n' % |
| 139 re.sub('\\n', '\n ', stack_trace)) |
| 140 else: |
| 141 err = '%s\n' % message |
| 142 try: |
| 143 sys.stderr.write(err.encode(UTF8)) |
| 144 except UnicodeDecodeError: |
| 145 # Can happen when outputting invalid Unicode filenames. |
| 146 sys.stderr.write(err) |
| 147 sys.exit(1) |
| 148 |
| 149 |
| 150 def _OutputUsageAndExit(command_runner): |
| 151 command_runner.RunNamedCommand('help') |
| 152 sys.exit(1) |
| 153 |
| 154 |
| 155 class GsutilFormatter(logging.Formatter): |
| 156 """A logging.Formatter that supports logging microseconds (%f).""" |
| 157 |
| 158 def formatTime(self, record, datefmt=None): |
| 159 if datefmt: |
| 160 return datetime.datetime.fromtimestamp(record.created).strftime(datefmt) |
| 161 |
| 162 # Use default implementation if datefmt is not specified. |
| 163 return super(GsutilFormatter, self).formatTime(record, datefmt=datefmt) |
| 164 |
| 165 |
| 166 def _ConfigureLogging(level=logging.INFO): |
| 167 """Similar to logging.basicConfig() except it always adds a handler.""" |
| 168 log_format = '%(levelname)s %(asctime)s %(filename)s] %(message)s' |
| 169 date_format = '%m%d %H:%M:%S.%f' |
| 170 formatter = GsutilFormatter(fmt=log_format, datefmt=date_format) |
| 171 handler = GsutilStreamHandler() |
| 172 handler.setFormatter(formatter) |
| 173 root_logger = logging.getLogger() |
| 174 root_logger.addHandler(handler) |
| 175 root_logger.setLevel(level) |
| 176 |
| 177 |
| 178 def main(): |
| 179 InitializeSignalHandling() |
| 180 # Any modules used in initializing multiprocessing variables must be |
| 181 # imported after importing gslib.__main__. |
| 182 # pylint: disable=redefined-outer-name,g-import-not-at-top |
| 183 import gslib.boto_translation |
| 184 import gslib.command |
| 185 import gslib.util |
| 186 from gslib.util import BOTO_IS_SECURE |
| 187 from gslib.util import CERTIFICATE_VALIDATION_ENABLED |
| 188 # pylint: disable=unused-variable |
| 189 from gcs_oauth2_boto_plugin import oauth2_client |
| 190 # pylint: enable=unused-variable |
| 191 from gslib.util import MultiprocessingIsAvailable |
| 192 if MultiprocessingIsAvailable()[0]: |
| 193 # These setup methods must be called, and, on Windows, they can only be |
| 194 # called from within an "if __name__ == '__main__':" block. |
| 195 gslib.util.InitializeMultiprocessingVariables() |
| 196 gslib.command.InitializeMultiprocessingVariables() |
| 197 gslib.boto_translation.InitializeMultiprocessingVariables() |
| 198 |
| 199 # This needs to be done after gslib.util.InitializeMultiprocessingVariables(), |
| 200 # since otherwise we can't call gslib.util.CreateLock. |
| 201 try: |
| 202 # pylint: disable=unused-import,g-import-not-at-top |
| 203 import gcs_oauth2_boto_plugin |
| 204 gcs_oauth2_boto_plugin.oauth2_helper.SetFallbackClientIdAndSecret( |
| 205 GSUTIL_CLIENT_ID, GSUTIL_CLIENT_NOTSOSECRET) |
| 206 gcs_oauth2_boto_plugin.oauth2_helper.SetLock(CreateLock()) |
| 207 except ImportError: |
| 208 pass |
| 209 |
| 210 global debug |
| 211 global test_exception_traces |
| 212 |
| 213 if not (2, 6) <= sys.version_info[:3] < (3,): |
| 214 raise gslib.exception.CommandException( |
| 215 'gsutil requires python 2.6 or 2.7.') |
| 216 |
| 217 # In gsutil 4.0 and beyond, we don't use the boto library for the JSON |
| 218 # API. However, we still store gsutil configuration data in the .boto |
| 219 # config file for compatibility with previous versions and user convenience. |
| 220 # Many users have a .boto configuration file from previous versions, and it |
| 221 # is useful to have all of the configuration for gsutil stored in one place. |
| 222 command_runner = CommandRunner() |
| 223 if not BOTO_IS_SECURE: |
| 224 raise CommandException('\n'.join(textwrap.wrap( |
| 225 'Your boto configuration has is_secure = False. Gsutil cannot be ' |
| 226 'run this way, for security reasons.'))) |
| 227 |
| 228 headers = {} |
| 229 parallel_operations = False |
| 230 quiet = False |
| 231 version = False |
| 232 debug = 0 |
| 233 test_exception_traces = False |
| 234 |
| 235 # If user enters no commands just print the usage info. |
| 236 if len(sys.argv) == 1: |
| 237 sys.argv.append('help') |
| 238 |
| 239 # Change the default of the 'https_validate_certificates' boto option to |
| 240 # True (it is currently False in boto). |
| 241 if not boto.config.has_option('Boto', 'https_validate_certificates'): |
| 242 if not boto.config.has_section('Boto'): |
| 243 boto.config.add_section('Boto') |
| 244 boto.config.setbool('Boto', 'https_validate_certificates', True) |
| 245 |
| 246 gslib.util.certs_file_lock = CreateLock() |
| 247 for signal_num in GetCaughtSignals(): |
| 248 RegisterSignalHandler(signal_num, _CleanupSignalHandler) |
| 249 GetCertsFile() |
| 250 |
| 251 try: |
| 252 try: |
| 253 opts, args = getopt.getopt(sys.argv[1:], 'dDvo:h:mq', |
| 254 ['debug', 'detailedDebug', 'version', 'option', |
| 255 'help', 'header', 'multithreaded', 'quiet', |
| 256 'testexceptiontraces']) |
| 257 except getopt.GetoptError as e: |
| 258 _HandleCommandException(gslib.exception.CommandException(e.msg)) |
| 259 for o, a in opts: |
| 260 if o in ('-d', '--debug'): |
| 261 # Passing debug=2 causes boto to include httplib header output. |
| 262 debug = 3 |
| 263 elif o in ('-D', '--detailedDebug'): |
| 264 # We use debug level 3 to ask gsutil code to output more detailed |
| 265 # debug output. This is a bit of a hack since it overloads the same |
| 266 # flag that was originally implemented for boto use. And we use -DD |
| 267 # to ask for really detailed debugging (i.e., including HTTP payload). |
| 268 if debug == 3: |
| 269 debug = 4 |
| 270 else: |
| 271 debug = 3 |
| 272 elif o in ('-?', '--help'): |
| 273 _OutputUsageAndExit(command_runner) |
| 274 elif o in ('-h', '--header'): |
| 275 (hdr_name, _, hdr_val) = a.partition(':') |
| 276 if not hdr_name: |
| 277 _OutputUsageAndExit(command_runner) |
| 278 headers[hdr_name.lower()] = hdr_val |
| 279 elif o in ('-m', '--multithreaded'): |
| 280 parallel_operations = True |
| 281 elif o in ('-q', '--quiet'): |
| 282 quiet = True |
| 283 elif o in ('-v', '--version'): |
| 284 version = True |
| 285 elif o == '--testexceptiontraces': # Hidden flag for integration tests. |
| 286 test_exception_traces = True |
| 287 elif o in ('-o', '--option'): |
| 288 (opt_section_name, _, opt_value) = a.partition('=') |
| 289 if not opt_section_name: |
| 290 _OutputUsageAndExit(command_runner) |
| 291 (opt_section, _, opt_name) = opt_section_name.partition(':') |
| 292 if not opt_section or not opt_name: |
| 293 _OutputUsageAndExit(command_runner) |
| 294 if not boto.config.has_section(opt_section): |
| 295 boto.config.add_section(opt_section) |
| 296 boto.config.set(opt_section, opt_name, opt_value) |
| 297 httplib2.debuglevel = debug |
| 298 if debug > 1: |
| 299 sys.stderr.write(DEBUG_WARNING) |
| 300 if debug >= 2: |
| 301 _ConfigureLogging(level=logging.DEBUG) |
| 302 command_runner.RunNamedCommand('ver', ['-l']) |
| 303 config_items = [] |
| 304 try: |
| 305 config_items.extend(boto.config.items('Boto')) |
| 306 config_items.extend(boto.config.items('GSUtil')) |
| 307 except ConfigParser.NoSectionError: |
| 308 pass |
| 309 for i in xrange(len(config_items)): |
| 310 config_item_key = config_items[i][0] |
| 311 if config_item_key in CONFIG_KEYS_TO_REDACT: |
| 312 config_items[i] = (config_item_key, 'REDACTED') |
| 313 sys.stderr.write('Command being run: %s\n' % ' '.join(sys.argv)) |
| 314 sys.stderr.write('config_file_list: %s\n' % GetBotoConfigFileList()) |
| 315 sys.stderr.write('config: %s\n' % str(config_items)) |
| 316 elif quiet: |
| 317 _ConfigureLogging(level=logging.WARNING) |
| 318 else: |
| 319 _ConfigureLogging(level=logging.INFO) |
| 320 # oauth2client uses info logging in places that would better |
| 321 # correspond to gsutil's debug logging (e.g., when refreshing |
| 322 # access tokens). |
| 323 oauth2client.client.logger.setLevel(logging.WARNING) |
| 324 |
| 325 if not CERTIFICATE_VALIDATION_ENABLED: |
| 326 sys.stderr.write(HTTP_WARNING) |
| 327 |
| 328 if version: |
| 329 command_name = 'version' |
| 330 elif not args: |
| 331 command_name = 'help' |
| 332 else: |
| 333 command_name = args[0] |
| 334 |
| 335 _CheckAndWarnForProxyDifferences() |
| 336 |
| 337 if os.environ.get('_ARGCOMPLETE', '0') == '1': |
| 338 return _PerformTabCompletion(command_runner) |
| 339 |
| 340 return _RunNamedCommandAndHandleExceptions( |
| 341 command_runner, command_name, args=args[1:], headers=headers, |
| 342 debug_level=debug, parallel_operations=parallel_operations) |
| 343 finally: |
| 344 _Cleanup() |
| 345 |
| 346 |
| 347 def _CheckAndWarnForProxyDifferences(): |
| 348 # If there are both boto config and environment variable config present for |
| 349 # proxies, unset the environment variable and warn if it differs. |
| 350 boto_port = boto.config.getint('Boto', 'proxy_port', 0) |
| 351 if boto.config.get('Boto', 'proxy', None) or boto_port: |
| 352 for proxy_env_var in ['http_proxy', 'https_proxy', 'HTTPS_PROXY']: |
| 353 if proxy_env_var in os.environ and os.environ[proxy_env_var]: |
| 354 differing_values = [] |
| 355 proxy_info = ProxyInfoFromEnvironmentVar(proxy_env_var) |
| 356 if proxy_info.proxy_host != boto.config.get('Boto', 'proxy', None): |
| 357 differing_values.append( |
| 358 'Boto proxy host: "%s" differs from %s proxy host: "%s"' % |
| 359 (boto.config.get('Boto', 'proxy', None), proxy_env_var, |
| 360 proxy_info.proxy_host)) |
| 361 if (proxy_info.proxy_user != |
| 362 boto.config.get('Boto', 'proxy_user', None)): |
| 363 differing_values.append( |
| 364 'Boto proxy user: "%s" differs from %s proxy user: "%s"' % |
| 365 (boto.config.get('Boto', 'proxy_user', None), proxy_env_var, |
| 366 proxy_info.proxy_user)) |
| 367 if (proxy_info.proxy_pass != |
| 368 boto.config.get('Boto', 'proxy_pass', None)): |
| 369 differing_values.append( |
| 370 'Boto proxy password differs from %s proxy password' % |
| 371 proxy_env_var) |
| 372 # Only compare ports if at least one is present, since the |
| 373 # boto logic for selecting default ports has not yet executed. |
| 374 if ((proxy_info.proxy_port or boto_port) and |
| 375 proxy_info.proxy_port != boto_port): |
| 376 differing_values.append( |
| 377 'Boto proxy port: "%s" differs from %s proxy port: "%s"' % |
| 378 (boto_port, proxy_env_var, proxy_info.proxy_port)) |
| 379 if differing_values: |
| 380 sys.stderr.write('\n'.join(textwrap.wrap( |
| 381 'WARNING: Proxy configuration is present in both the %s ' |
| 382 'environment variable and boto configuration, but ' |
| 383 'configuration differs. boto configuration proxy values will ' |
| 384 'be used. Differences detected:' % proxy_env_var))) |
| 385 sys.stderr.write('\n%s\n' % '\n'.join(differing_values)) |
| 386 # Regardless of whether the proxy configuration values matched, |
| 387 # delete the environment variable so as not to confuse boto. |
| 388 del os.environ[proxy_env_var] |
| 389 |
| 390 |
| 391 def _HandleUnknownFailure(e): |
| 392 # Called if we fall through all known/handled exceptions. Allows us to |
| 393 # print a stacktrace if -D option used. |
| 394 if debug >= 2: |
| 395 stack_trace = traceback.format_exc() |
| 396 sys.stderr.write('DEBUG: Exception stack trace:\n %s\n' % |
| 397 re.sub('\\n', '\n ', stack_trace)) |
| 398 else: |
| 399 _OutputAndExit('Failure: %s.' % e) |
| 400 |
| 401 |
| 402 def _HandleCommandException(e): |
| 403 if e.informational: |
| 404 _OutputAndExit(e.reason) |
| 405 else: |
| 406 _OutputAndExit('CommandException: %s' % e.reason) |
| 407 |
| 408 |
| 409 # pylint: disable=unused-argument |
| 410 def _HandleControlC(signal_num, cur_stack_frame): |
| 411 """Called when user hits ^C. |
| 412 |
| 413 This function prints a brief message instead of the normal Python stack trace |
| 414 (unless -D option is used). |
| 415 |
| 416 Args: |
| 417 signal_num: Signal that was caught. |
| 418 cur_stack_frame: Unused. |
| 419 """ |
| 420 if debug >= 2: |
| 421 stack_trace = ''.join(traceback.format_list(traceback.extract_stack())) |
| 422 _OutputAndExit( |
| 423 'DEBUG: Caught signal %d - Exception stack trace:\n' |
| 424 ' %s' % (signal_num, re.sub('\\n', '\n ', stack_trace))) |
| 425 else: |
| 426 _OutputAndExit('Caught signal %d - exiting' % signal_num) |
| 427 |
| 428 |
| 429 def _HandleSigQuit(signal_num, cur_stack_frame): |
| 430 """Called when user hits ^\\, so we can force breakpoint a running gsutil.""" |
| 431 import pdb # pylint: disable=g-import-not-at-top |
| 432 pdb.set_trace() |
| 433 |
| 434 |
| 435 def _ConstructAccountProblemHelp(reason): |
| 436 """Constructs a help string for an access control error. |
| 437 |
| 438 Args: |
| 439 reason: e.reason string from caught exception. |
| 440 |
| 441 Returns: |
| 442 Contructed help text. |
| 443 """ |
| 444 default_project_id = boto.config.get_value('GSUtil', 'default_project_id') |
| 445 # pylint: disable=line-too-long, g-inconsistent-quotes |
| 446 acct_help = ( |
| 447 "Your request resulted in an AccountProblem (403) error. Usually this " |
| 448 "happens if you attempt to create a bucket without first having " |
| 449 "enabled billing for the project you are using. Please ensure billing is " |
| 450 "enabled for your project by following the instructions at " |
| 451 "`Google Developers Console<https://developers.google.com/console/help/bil
ling>`. ") |
| 452 if default_project_id: |
| 453 acct_help += ( |
| 454 "In the project overview, ensure that the Project Number listed for " |
| 455 "your project matches the project ID (%s) from your boto config file. " |
| 456 % default_project_id) |
| 457 acct_help += ( |
| 458 "If the above doesn't resolve your AccountProblem, please send mail to " |
| 459 "gs-team@google.com requesting assistance, noting the exact command you " |
| 460 "ran, the fact that you received a 403 AccountProblem error, and your " |
| 461 "project ID. Please do not post your project ID on StackOverflow. " |
| 462 "Note: It's possible to use Google Cloud Storage without enabling " |
| 463 "billing if you're only listing or reading objects for which you're " |
| 464 "authorized, or if you're uploading objects to a bucket billed to a " |
| 465 "project that has billing enabled. But if you're attempting to create " |
| 466 "buckets or upload objects to a bucket owned by your own project, you " |
| 467 "must first enable billing for that project.") |
| 468 return acct_help |
| 469 |
| 470 |
| 471 def _CheckAndHandleCredentialException(e, args): |
| 472 # Provide detail to users who have no boto config file (who might previously |
| 473 # have been using gsutil only for accessing publicly readable buckets and |
| 474 # objects). |
| 475 # pylint: disable=g-import-not-at-top |
| 476 from gslib.util import HasConfiguredCredentials |
| 477 if (not HasConfiguredCredentials() and |
| 478 not boto.config.get_value('Tests', 'bypass_anonymous_access_warning', |
| 479 False)): |
| 480 # The check above allows tests to assert that we get a particular, |
| 481 # expected failure, rather than always encountering this error message |
| 482 # when there are no configured credentials. This allows tests to |
| 483 # simulate a second user without permissions, without actually requiring |
| 484 # two separate configured users. |
| 485 if os.environ.get('CLOUDSDK_WRAPPER') == '1': |
| 486 _OutputAndExit('\n'.join(textwrap.wrap( |
| 487 'You are attempting to access protected data with no configured ' |
| 488 'credentials. Please visit ' |
| 489 'https://cloud.google.com/console#/project and sign up for an ' |
| 490 'account, and then run the "gcloud auth login" command to ' |
| 491 'configure gsutil to use these credentials.'))) |
| 492 else: |
| 493 _OutputAndExit('\n'.join(textwrap.wrap( |
| 494 'You are attempting to access protected data with no configured ' |
| 495 'credentials. Please visit ' |
| 496 'https://cloud.google.com/console#/project and sign up for an ' |
| 497 'account, and then run the "gsutil config" command to configure ' |
| 498 'gsutil to use these credentials.'))) |
| 499 elif (e.reason and |
| 500 (e.reason == 'AccountProblem' or e.reason == 'Account disabled.' or |
| 501 'account for the specified project has been disabled' in e.reason) |
| 502 and ','.join(args).find('gs://') != -1): |
| 503 _OutputAndExit('\n'.join(textwrap.wrap( |
| 504 _ConstructAccountProblemHelp(e.reason)))) |
| 505 |
| 506 |
| 507 def _RunNamedCommandAndHandleExceptions(command_runner, command_name, args=None, |
| 508 headers=None, debug_level=0, |
| 509 parallel_operations=False): |
| 510 """Runs the command with the given command runner and arguments.""" |
| 511 # pylint: disable=g-import-not-at-top |
| 512 from gslib.util import GetConfigFilePath |
| 513 from gslib.util import IS_WINDOWS |
| 514 from gslib.util import IsRunningInteractively |
| 515 try: |
| 516 # Catch ^C so we can print a brief message instead of the normal Python |
| 517 # stack trace. Register as a final signal handler because this handler kills |
| 518 # the main gsutil process (so it must run last). |
| 519 RegisterSignalHandler(signal.SIGINT, _HandleControlC, is_final_handler=True) |
| 520 # Catch ^\ so we can force a breakpoint in a running gsutil. |
| 521 if not IS_WINDOWS: |
| 522 RegisterSignalHandler(signal.SIGQUIT, _HandleSigQuit) |
| 523 return command_runner.RunNamedCommand(command_name, args, headers, |
| 524 debug_level, parallel_operations) |
| 525 except AttributeError as e: |
| 526 if str(e).find('secret_access_key') != -1: |
| 527 _OutputAndExit('Missing credentials for the given URI(s). Does your ' |
| 528 'boto config file contain all needed credentials?') |
| 529 else: |
| 530 _OutputAndExit(str(e)) |
| 531 except gslib.exception.CommandException as e: |
| 532 _HandleCommandException(e) |
| 533 except getopt.GetoptError as e: |
| 534 _HandleCommandException(gslib.exception.CommandException(e.msg)) |
| 535 except boto.exception.InvalidUriError as e: |
| 536 _OutputAndExit('InvalidUriError: %s.' % e.message) |
| 537 except gslib.exception.InvalidUrlError as e: |
| 538 _OutputAndExit('InvalidUrlError: %s.' % e.message) |
| 539 except boto.auth_handler.NotReadyToAuthenticate: |
| 540 _OutputAndExit('NotReadyToAuthenticate') |
| 541 except OSError as e: |
| 542 _OutputAndExit('OSError: %s.' % e.strerror) |
| 543 except IOError as e: |
| 544 if (e.errno == errno.EPIPE or (IS_WINDOWS and e.errno == errno.EINVAL) |
| 545 and not IsRunningInteractively()): |
| 546 # If we get a pipe error, this just means that the pipe to stdout or |
| 547 # stderr is broken. This can happen if the user pipes gsutil to a command |
| 548 # that doesn't use the entire output stream. Instead of raising an error, |
| 549 # just swallow it up and exit cleanly. |
| 550 sys.exit(0) |
| 551 else: |
| 552 raise |
| 553 except wildcard_iterator.WildcardException as e: |
| 554 _OutputAndExit(e.reason) |
| 555 except ProjectIdException as e: |
| 556 _OutputAndExit( |
| 557 'You are attempting to perform an operation that requires a ' |
| 558 'project id, with none configured. Please re-run ' |
| 559 'gsutil config and make sure to follow the instructions for ' |
| 560 'finding and entering your default project id.') |
| 561 except BadRequestException as e: |
| 562 if e.reason == 'MissingSecurityHeader': |
| 563 _CheckAndHandleCredentialException(e, args) |
| 564 _OutputAndExit(e) |
| 565 except AccessDeniedException as e: |
| 566 _CheckAndHandleCredentialException(e, args) |
| 567 _OutputAndExit(e) |
| 568 except ArgumentException as e: |
| 569 _OutputAndExit(e) |
| 570 except ServiceException as e: |
| 571 _OutputAndExit(e) |
| 572 except apitools_exceptions.HttpError as e: |
| 573 # These should usually be retried by the underlying implementation or |
| 574 # wrapped by CloudApi ServiceExceptions, but if we do get them, |
| 575 # print something useful. |
| 576 _OutputAndExit('HttpError: %s, %s' % (getattr(e.response, 'status', ''), |
| 577 e.content or '')) |
| 578 except socket.error as e: |
| 579 if e.args[0] == errno.EPIPE: |
| 580 # Retrying with a smaller file (per suggestion below) works because |
| 581 # the library code send loop (in boto/s3/key.py) can get through the |
| 582 # entire file and then request the HTTP response before the socket |
| 583 # gets closed and the response lost. |
| 584 _OutputAndExit( |
| 585 'Got a "Broken pipe" error. This can happen to clients using Python ' |
| 586 '2.x, when the server sends an error response and then closes the ' |
| 587 'socket (see http://bugs.python.org/issue5542). If you are trying to ' |
| 588 'upload a large object you might retry with a small (say 200k) ' |
| 589 'object, and see if you get a more specific error code.' |
| 590 ) |
| 591 else: |
| 592 _HandleUnknownFailure(e) |
| 593 except Exception as e: |
| 594 # Check for two types of errors related to service accounts. These errors |
| 595 # appear to be the same except for their messages, but they are caused by |
| 596 # different problems and both have unhelpful error messages. Moreover, |
| 597 # the error type belongs to PyOpenSSL, which is not necessarily installed. |
| 598 if 'mac verify failure' in str(e): |
| 599 _OutputAndExit( |
| 600 'Encountered an error while refreshing access token. ' |
| 601 'If you are using a service account,\nplease verify that the ' |
| 602 'gs_service_key_file_password field in your config file,' |
| 603 '\n%s, is correct.' % GetConfigFilePath()) |
| 604 elif 'asn1 encoding routines' in str(e): |
| 605 _OutputAndExit( |
| 606 'Encountered an error while refreshing access token. ' |
| 607 'If you are using a service account,\nplease verify that the ' |
| 608 'gs_service_key_file field in your config file,\n%s, is correct.' |
| 609 % GetConfigFilePath()) |
| 610 _HandleUnknownFailure(e) |
| 611 |
| 612 |
| 613 def _PerformTabCompletion(command_runner): |
| 614 """Performs gsutil-specific tab completion for the shell.""" |
| 615 # argparse and argcomplete are bundled with the Google Cloud SDK. |
| 616 # When gsutil is invoked from the Google Cloud SDK, both should be available. |
| 617 try: |
| 618 import argcomplete |
| 619 import argparse |
| 620 except ImportError as e: |
| 621 _OutputAndExit('A library required for performing tab completion was' |
| 622 ' not found.\nCause: %s' % e) |
| 623 parser = argparse.ArgumentParser(add_help=False) |
| 624 subparsers = parser.add_subparsers() |
| 625 command_runner.ConfigureCommandArgumentParsers(subparsers) |
| 626 argcomplete.autocomplete(parser, exit_method=sys.exit) |
| 627 |
| 628 return 0 |
| 629 |
| 630 if __name__ == '__main__': |
| 631 sys.exit(main()) |
OLD | NEW |