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

Side by Side 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, 7 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 unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 # Copyright 2010 Google Inc.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 """Implementation of gsutil commands."""
16
17 import ctypes
18 import datetime
19 import gzip
20 import mimetypes
21 import os
22 import platform
23 import re
24 import shutil
25 import signal
26 import sys
27 import tarfile
28 import tempfile
29 import time
30 import webbrowser
31 import xml.dom.minidom
32 import xml.sax.xmlreader
33 import boto
34 import boto.s3.connection
35
36 from boto import handler
37 from boto.gs.resumable_upload_handler import ResumableUploadHandler
38 from boto.provider import Provider
39 from boto.pyami.config import BotoConfigLocations
40 from boto.s3.resumable_download_handler import ResumableDownloadHandler
41 from boto.storage_uri import BucketStorageUri
42 from exception import CommandException
43 from gslib.project_id import ProjectIdHandler
44 import wildcard_iterator
45 from wildcard_iterator import ContainsWildcard
46 from wildcard_iterator import ResultType
47 from wildcard_iterator import WildcardException
48
49 GOOG_API_CONSOLE_URI = "http://code.google.com/apis/console"
50
51 _HAVE_OAUTH2 = False
52 try:
53 from oauth2_plugin import oauth2_helper
54 _HAVE_OAUTH2 = True
55 except ImportError:
56 pass
57
58 # Enum class for specifying listing style.
59 class ListingStyle(object):
60 SHORT = 'SHORT'
61 LONG = 'LONG'
62 LONG_LONG = 'LONG_LONG'
63
64
65 # Binary exponentiation strings.
66 EXP_STRINGS = [
67 (0, 'B'),
68 (10, 'KB'),
69 (20, 'MB'),
70 (30, 'GB'),
71 (40, 'TB'),
72 (50, 'PB'),
73 ]
74
75 ONE_MB = 1024*1024
76
77 SCOPE_FULL_CONTROL = 'https://www.googleapis.com/auth/devstorage.full_control'
78 SCOPE_READ_WRITE = 'https://www.googleapis.com/auth/devstorage.read_write'
79 SCOPE_READ_ONLY = 'https://www.googleapis.com/auth/devstorage.read_only'
80
81 CONFIG_PRELUDE_CONTENT = """
82 # This file contains credentials and other configuration information needed
83 # by the boto library, used by gsutil. You can edit this file (e.g., to add
84 # credentials) but be careful not to mis-edit any of the variable names (like
85 # "gs_access_key_id") or remove important markers (like the "[Credentials]" and
86 # "[Boto]" section delimeters).
87 #
88 """
89
90 CONFIG_BOTO_SECTION_CONTENT = """
91 [Boto]
92
93 # To use a proxy, edit and uncomment the proxy and proxy_port lines. If you
94 # need a user/password with this proxy, edit and uncomment those lines as well.
95 #proxy = <proxy host>
96 #proxy_port = <proxy port>
97 #proxy_user = <your proxy user name>
98 #proxy_pass = <your proxy password>
99
100 # The following two options control the use of a secure transport for requests
101 # to S3 and Google Storage. It is highly recommended to set both options to
102 # True in production environments, especially when using OAuth2 bearer token
103 # authentication with Google Storage.
104
105 # Set 'is_secure' to False to cause boto to connect using HTTP instead of the
106 # default HTTPS. This is useful if you want to capture/analyze traffic
107 # (e.g., with tcpdump). This option should always be set to True in production
108 # environments.
109 #is_secure = False
110
111 # Set 'https_validate_certificates' to False to disable server certificate
112 # checking. This is useful if you want to capture/analyze traffic using an
113 # intercepting proxy. This option should always be set to True in production
114 # environments.
115 # In gsutil, the default for this option is True. *However*, the default for
116 # this option in the boto library itself is currently 'False'; it is therefore
117 # recommended to always set this option explicitly to True in configuration
118 # files.
119 https_validate_certificates = True
120
121 # 'debug' controls the level of debug messages printed: 0 for none, 1
122 # for basic boto debug, 2 for all boto debug plus HTTP requests/responses.
123 # Note: 'gsutil -d' sets debug to 2 for that one command run.
124 #debug = <0, 1, or 2>
125
126 # 'num_retries' controls the number of retry attempts made when errors occur.
127 # The default is 5. Note: don't set this value to 0, as it will cause boto to
128 # fail when reusing HTTP connections.
129 #num_retries = <integer value>
130 """
131
132 CONFIG_INPUTLESS_GSUTIL_SECTION_CONTENT = """
133 [GSUtil]
134
135 # 'resumable_threshold' specifies the smallest file size [bytes] for which
136 # resumable Google Storage transfers are attempted. The default is 1048576
137 # (1MB).
138 #resumable_threshold = 1048576
139
140 # 'resumable_tracker_dir' specifies the base location where resumable
141 # transfer tracker files are saved. By default they're in ~/.gsutil
142 #resumable_tracker_dir = <file path>
143
144 # 'default_api_version' specifies the default Google Storage API version to
145 # use use. If not set below gsutil defaults to API version 1.
146 default_api_version = 2
147 """
148
149 CONFIG_OAUTH2_CONFIG_CONTENT = """
150 [OAuth2]
151 # This section specifies options used with OAuth2 authentication.
152
153 # 'token_cache' specifies how the OAuth2 client should cache access tokens.
154 # Valid values are:
155 # 'in_memory': an in-memory cache is used. This is only useful if the boto
156 # client instance (and with it the OAuth2 plugin instance) persists
157 # across multiple requests.
158 # 'file_system' : access tokens will be cached in the file system, in files
159 # whose names include a key derived from the refresh token the access token
160 # based on.
161 # The default is 'file_system'.
162 #token_cache = file_system
163 #token_cache = in_memory
164
165 # 'token_cache_path_pattern' specifies a path pattern for token cache files.
166 # This option is only relevant if token_cache = file_system.
167 # The value of this option should be a path, with place-holders '%(key)s' (which
168 # will be replaced with a key derived from the refresh token the cached access
169 # token was based on), and (optionally), %(uid)s (which will be replaced with
170 # the UID of the current user, if available via os.getuid()).
171 # Note that the config parser itself interpolates '%' placeholders, and hence
172 # the above placeholders need to be escaped as '%%(key)s'.
173 # The default value of this option is
174 # token_cache_path_pattern = <tmpdir>/oauth2client-tokencache.%%(uid)s.%%(key)s
175 # where <tmpdir> is the system-dependent default temp directory.
176
177 # The following options specify the OAuth2 client identity and secret that is
178 # used when requesting and using OAuth2 tokens. If not specified, a default
179 # OAuth2 client for the gsutil tool is used; for uses of the boto library (with
180 # OAuth2 authentication plugin) in other client software, it is recommended to
181 # use a tool/client-specific OAuth2 client. For more information on OAuth2, see
182 # http://code.google.com/apis/accounts/docs/OAuth2.html
183 #client_id = <OAuth2 client id>
184 #client_secret = <OAuth2 client secret>
185
186 # The following options specify the label and endpoint URIs for the OAUth2
187 # authorization provider being used. Primarily useful for tool developers.
188 #provider_label = Google
189 #provider_authorization_uri = https://accounts.google.com/o/oauth2/auth
190 #provider_token_uri = https://accounts.google.com/o/oauth2/token
191 """
192
193 CONFIG_COMMAND_HELP = """
194 Help on the gsutil config command:
195 gsutil [-D] config [OPTION]
196
197 The gsutil config command obtains access credentials for Google Storage, and
198 writes a boto/gsutil configuration file with the obtained credentials.
199
200 Unless specified otherwise, the configuration file is written to the default
201 config file path '%s'. If the default config file already exists, an attempt
202 is made to rename the existing file to a backup file '%s'; if that attempt
203 fails the command will exit.
204
205 A different destination file can be specified with the -o <file> option (use
206 '-o -' to write the config to standard output). If the specified file already
207 exists, the command will fail.
208
209 By default, gsutil config obtains OAuth2 tokens as follows (for background
210 on OAuth2, see http://code.google.com/apis/accounts/docs/OAuth2.html):
211 The command asks the user to open a web broswer to a URL for Google's
212 OAuth2 authorization page. In the browser, the user will be asked to sign
213 into the user's Google Account, unless already signed in. The user is then
214 prompted to authorize gsutil to access the user's Google Storage account
215 on the user's behalf. If the user approves the request, a verification
216 code is shown. The gsutil config command prompts for this verification
217 code, which is used to obtain an OAuth2 token that is written to the
218 configuration file.
219
220 The -b option can be used to instruct gsutil config to launch a browser,
221 (using python's webbrowser module) to navigate to Google's OAuth2
222 authorization page. Note that this will probably not work as expected
223 if you are running gsutil from an ssh window, or using gsutil on Windows.
224
225 The -r, -w, -f options cause gsutil config to request a token with restricted
226 scope; the resulting token will be restricted to read-only operations,
227 read-write operation, or all operations (including getacl/setacl operations).
228 In addition, -s <scope> can be used to request additional (non-Google-Storage)
229 scopes.
230
231 If no explicit scope option is given, -f (full control) is assumed by default.
232
233 The -a option can be used to prompt for Google Storage access key and secret
234 instead.
235
236 Options:
237 -h Print this help.
238 -a Prompt for Google Storage access key and secret instead of
239 obtaining an OAuth2 token.
240 -b Launch browser to obtain OAuth2 approval and project ID instead
241 of showing the URL and asking user to open the browser.
242 -D Print debug output.
243 -f Request token with full-control access (default).
244 -o <file> Write the configuration to <file> (use '-' for stdout)
245 -r Request token restricted to read-only access.
246 -s <scope> Request additional OAuth2 <scope>.
247 -w Request token restricted to read-write access.
248
249 """
250
251 def MakeHumanReadable(num):
252 """Generates human readable string for a number.
253
254 Args:
255 num: the number
256
257 Returns:
258 A string form of the number using size abbreviations (KB, MB, etc.)
259 """
260 i = 0
261 while i+1 < len(EXP_STRINGS) and num >= (2 ** EXP_STRINGS[i+1][0]):
262 i += 1
263 rounded_val = round(float(num) / 2 ** EXP_STRINGS[i][0], 2)
264 return '%s %s' % (rounded_val, EXP_STRINGS[i][1])
265
266
267 def UriStrFor(iterated_uri, obj):
268 """Constructs a StorageUri string for the given iterated_uri and object.
269
270 For example if we were iterating gs://*, obj could be an object in one
271 of the user's buckets enumerated by the ls command.
272
273 Args:
274 iterated_uri: base StorageUri being iterated.
275 obj: object being listed.
276
277 Returns:
278 URI string.
279 """
280 return '%s://%s/%s' % (iterated_uri.scheme, obj.bucket.name, obj.name)
281
282
283 def OpenConfigFile(file_path):
284 """Creates and opens a configuration file for writing.
285
286 The file is created with mode 0600, and attempts to open existing files will
287 fail (the latter is important to prevent symlink attacks).
288
289 It is the caller's responsibility to close the file.
290
291 Args:
292 file_path: Path of the file to be created.
293
294 Returns:
295 A writable file object for the opened file.
296
297 Raises:
298 CommandException: if an error occurred when opening the file (including when
299 the file already exists).
300 """
301 flags = os.O_RDWR | os.O_CREAT | os.O_EXCL
302 # Accommodate Windows; stolen from python2.6/tempfile.py.
303 if hasattr(os, 'O_NOINHERIT'):
304 flags |= os.O_NOINHERIT
305 try:
306 fd = os.open(file_path, flags, 0600)
307 except (OSError, IOError), e:
308 raise CommandException("Failed to open %s for writing: %s" %
309 (file_path, e))
310 return os.fdopen(fd, "w")
311
312
313 class Command(object):
314 """Class that contains all gsutil command code."""
315
316 def __init__(self, gsutil_bin_dir, boto_lib_dir, usage_string,
317 config_file_list, bucket_storage_uri_class=BucketStorageUri):
318 """Instantiates Command class.
319
320 Args:
321 gsutil_bin_dir: bin dir from which gsutil is running.
322 boto_lib_dir: lib dir where boto runs.
323 usage_string: usage string to print when user makes command error.
324 config_file_list: config file list returned by GetBotoConfigFileList().
325 bucket_storage_uri_class: Class to instantiate for cloud StorageUris.
326 Settable for testing/mocking.
327 """
328 self.gsutil_bin_dir = gsutil_bin_dir
329 self.usage_string = usage_string
330 self.boto_lib_dir = boto_lib_dir
331 self.config_file_list = config_file_list
332 self.bucket_storage_uri_class = bucket_storage_uri_class
333
334 config = boto.config
335 self.proj_id_handler = ProjectIdHandler()
336
337 def OutputUsageAndExit(self):
338 sys.stderr.write(self.usage_string)
339 sys.exit(0)
340
341 def StorageUri(self, uri_str, debug=0, validate=True):
342 """
343 Helper to instantiate boto.StorageUri with gsutil default flag values.
344 Uses self.bucket_storage_uri_class to support mocking/testing.
345
346 Args:
347 uri_str: StorageUri naming bucket + optional object.
348 debug: debug level to pass in to boto connection (range 0..3).
349 validate: Whether to check for bucket name validity.
350
351 Returns:
352 boto.StorageUri for given uri_str.
353
354 Raises:
355 InvalidUriError: if uri_str not valid.
356 """
357 return boto.storage_uri(
358 uri_str, 'file', debug=debug, validate=validate,
359 bucket_storage_uri_class=self.bucket_storage_uri_class)
360
361 def CmdWildcardIterator(self, uri_or_str, result_type=ResultType.URIS,
362 headers=None, debug=0):
363 """
364 Helper to instantiate gslib.WildcardIterator, passing
365 self.bucket_storage_uri_class to support mocking/testing.
366 Args are same as gslib.WildcardIterator interface, but without the
367 bucket_storage_uri_class param (which is instead filled in from Command
368 class state).
369 """
370 return wildcard_iterator.wildcard_iterator(
371 uri_or_str, self.proj_id_handler, result_type=result_type,
372 bucket_storage_uri_class=self.bucket_storage_uri_class,
373 headers=headers, debug=debug)
374
375 def InsistUriNamesContainer(self, command, uri):
376 """Checks that URI names a directory or bucket.
377
378 Args:
379 command: command being run
380 uri: StorageUri to check
381
382 Raises:
383 CommandException: if errors encountered.
384 """
385 if uri.names_singleton():
386 raise CommandException('Destination StorageUri must name a bucket or '
387 'directory for the\nmultiple source form of the '
388 '"%s" command.' % command)
389
390 def CatCommand(self, args, sub_opts=None, headers=None, debug=0):
391 """Implementation of cat command.
392
393 Args:
394 args: command-line argument list.
395 sub_opts: list of command-specific options from getopt.
396 headers: dictionary containing optional HTTP headers to pass to boto.
397 debug: debug level to pass in to boto connection (range 0..3).
398
399 Raises:
400 CommandException: if errors encountered.
401 """
402 show_header = False
403 if sub_opts:
404 for o, unused_a in sub_opts:
405 if o == '-h':
406 show_header = True
407
408 printed_one = False
409 for uri_str in args:
410 for uri in self.CmdWildcardIterator(uri_str, headers=headers,
411 debug=debug):
412 if not uri.object_name:
413 raise CommandException('"cat" command must specify objects.')
414 if show_header:
415 if printed_one:
416 print
417 print '==> %s <==' % uri.__str__()
418 printed_one = True
419 tmp_file = tempfile.TemporaryFile()
420 key = uri.get_key(False, headers)
421 key.get_file(tmp_file, headers)
422 tmp_file.seek(0)
423 while True:
424 # Use 8k buffer size.
425 data = tmp_file.read(8192)
426 if not data:
427 break
428 sys.stdout.write(data)
429 tmp_file.close()
430
431 def SetAclCommand(self, args, unused_sub_opts=None, headers=None, debug=0):
432 """Implementation of setacl command.
433
434 Args:
435 args: command-line argument list.
436 unused_sub_opts: list of command-specific options from getopt.
437 headers: dictionary containing optional HTTP headers to pass to boto.
438 debug: debug level to pass in to boto connection (range 0..3).
439
440 Raises:
441 CommandException: if errors encountered.
442 """
443 acl_arg = args[0]
444 uri_args = args[1:]
445 provider = None
446 first_uri = None
447 # Do a first pass over all matched objects to disallow multi-provider
448 # setacl requests, because there are differences in the ACL models.
449 for uri_str in uri_args:
450 for uri in self.CmdWildcardIterator(uri_str, headers=headers,
451 debug=debug):
452 if not provider:
453 provider = uri.scheme
454 elif uri.scheme != provider:
455 raise CommandException('"setacl" command spanning providers not '
456 'allowed.')
457 if not first_uri:
458 first_uri = uri
459
460 # Get ACL object from connection for the first URI, for interpreting the
461 # ACL. This won't fail because the main startup code insists on 1 arg
462 # for this command.
463 storage_uri = first_uri
464 acl_class = storage_uri.acl_class()
465 canned_acls = storage_uri.canned_acls()
466
467 # Determine whether acl_arg names a file containing XML ACL text vs. the
468 # string name of a canned ACL.
469 if os.path.isfile(acl_arg):
470 acl_file = open(acl_arg, 'r')
471 acl_txt = acl_file.read()
472 acl_file.close()
473 acl_obj = acl_class()
474 h = handler.XmlHandler(acl_obj, storage_uri.get_bucket())
475 try:
476 xml.sax.parseString(acl_txt, h)
477 except xml.sax._exceptions.SAXParseException, e:
478 raise CommandException('Requested ACL is invalid: %s at line %s, '
479 'column %s' % (e.getMessage(), e.getLineNumber(),
480 e.getColumnNumber()))
481 acl_arg = acl_obj
482 else:
483 # No file exists, so expect a canned ACL string.
484 if acl_arg not in canned_acls:
485 raise CommandException('Invalid canned ACL "%s".' % acl_arg)
486
487 # Now iterate over URIs and set the ACL on each.
488 for uri_str in uri_args:
489 for uri in self.CmdWildcardIterator(uri_str, headers=headers,
490 debug=debug):
491 print 'Setting ACL on %s...' % uri
492 uri.set_acl(acl_arg, uri.object_name, False, headers)
493
494 def ExplainIfSudoNeeded(self, tf, dirs_to_remove):
495 """Explains what to do if sudo needed to update gsutil software.
496
497 Happens if gsutil was previously installed by a different user (typically if
498 someone originally installed in a shared file system location, using sudo).
499
500 Args:
501 tf: opened TarFile.
502 dirs_to_remove: list of directories to remove.
503
504 Raises:
505 CommandException: if errors encountered.
506 """
507 system = platform.system()
508 # If running under Windows we don't need (or have) sudo.
509 if system.lower().startswith('windows'):
510 return
511
512 user_id = os.getuid()
513 if (os.stat(self.gsutil_bin_dir).st_uid == user_id and
514 os.stat(self.boto_lib_dir).st_uid == user_id):
515 return
516
517 # Won't fail - this command runs after main startup code that insists on
518 # having a config file.
519 config_file = self.config_file_list
520 self.CleanUpUpdateCommand(tf, dirs_to_remove)
521 raise CommandException(
522 ('Since it was installed by a different user previously, you will need '
523 'to update using the following commands.\nYou will be prompted for '
524 'your password, and the install will run as "root". If you\'re unsure '
525 'what this means please ask your system administrator for help:'
526 '\n\tchmod 644 %s\n\tsudo env BOTO_CONFIG=%s gsutil update'
527 '\n\tchmod 600 %s') % (config_file, config_file, config_file),
528 informational=True)
529
530 # This list is checked during gsutil update by doing a lowercased
531 # slash-left-stripped check. For example "/Dev" would match the "dev" entry.
532 unsafe_update_dirs = [
533 'applications', 'auto', 'bin', 'boot', 'desktop', 'dev',
534 'documents and settings', 'etc', 'export', 'home', 'kernel', 'lib',
535 'lib32', 'library', 'lost+found', 'mach_kernel', 'media', 'mnt', 'net',
536 'null', 'network', 'opt', 'private', 'proc', 'program files', 'python',
537 'root', 'sbin', 'scripts', 'srv', 'sys', 'system', 'tmp', 'users', 'usr',
538 'var', 'volumes', 'win', 'win32', 'windows', 'winnt',
539 ]
540
541 def EnsureDirsSafeForUpdate(self, dirs):
542 """Throws Exception if any of dirs is known to be unsafe for gsutil update.
543
544 This provides a fail-safe check to ensure we don't try to overwrite
545 or delete any important directories. (That shouldn't happen given the
546 way we construct tmp dirs, etc., but since the gsutil update cleanup
547 use shutil.rmtree() it's prudent to add extra checks.)
548
549 Args:
550 dirs: list of directories to check.
551
552 Raises:
553 CommandException: If unsafe directory encountered.
554 """
555 for d in dirs:
556 if not d:
557 d = 'null'
558 if d.lstrip(os.sep).lower() in self.unsafe_update_dirs:
559 raise CommandException('EnsureDirsSafeForUpdate: encountered unsafe '
560 'directory (%s); aborting update' % d)
561
562 def CleanUpUpdateCommand(self, tf, dirs_to_remove):
563 """Cleans up temp files etc. from running update command.
564
565 Args:
566 tf: opened TarFile.
567 dirs_to_remove: list of directories to remove.
568
569 """
570 tf.close()
571 self.EnsureDirsSafeForUpdate(dirs_to_remove)
572 for directory in dirs_to_remove:
573 shutil.rmtree(directory)
574
575 def LoadVersionString(self):
576 """Loads version string for currently installed gsutil command.
577
578 Returns:
579 Version string.
580
581 Raises:
582 CommandException: if errors encountered.
583 """
584 ver_file_path = self.gsutil_bin_dir + os.sep + 'VERSION'
585 if not os.path.isfile(ver_file_path):
586 raise CommandException(
587 '%s not found. Did you install the\ncomplete gsutil software after '
588 'the gsutil "update" command was implemented?' % ver_file_path)
589 ver_file = open(ver_file_path, 'r')
590 installed_version_string = ver_file.read().rstrip('\n')
591 ver_file.close()
592 return installed_version_string
593
594 def UpdateCommand(self, unused_args, sub_opts=None, headers=None, debug=0):
595 """Implementation of experimental update command.
596
597 Args:
598 unused_args: command-line argument list.
599 sub_opts: list of command-specific options from getopt.
600 headers: dictionary containing optional HTTP headers to pass to boto.
601 debug: debug level to pass in to boto connection (range 0..3).
602
603 Raises:
604 CommandException: if errors encountered.
605 """
606 installed_version_string = self.LoadVersionString()
607
608 dirs_to_remove = []
609 # Retrieve gsutil tarball and check if it's newer than installed code.
610 # TODO: Store this version info as metadata on the tarball object and
611 # change this command's implementation to check that metadata instead of
612 # downloading the tarball to check the version info.
613 tmp_dir = tempfile.mkdtemp()
614 dirs_to_remove.append(tmp_dir)
615 os.chdir(tmp_dir)
616 print 'Checking for software update...'
617 self.CopyObjsCommand(['gs://pub/gsutil.tar.gz', 'file://gsutil.tar.gz'], [],
618 headers, debug)
619 tf = tarfile.open('gsutil.tar.gz')
620 tf.errorlevel = 1 # So fatal tarball unpack errors raise exceptions.
621 tf.extract('./gsutil/VERSION')
622 ver_file = open('gsutil/VERSION', 'r')
623 latest_version_string = ver_file.read().rstrip('\n')
624 ver_file.close()
625
626 # The force_update option works around a problem with the way the
627 # first gsutil "update" command exploded the gsutil and boto directories,
628 # which didn't correctly install boto. People running that older code can
629 # run "gsutil update" (to update to the newer gsutil update code) followed
630 # by "gsutil update -f" (which will then update the boto code, even though
631 # the VERSION is already the latest version).
632 force_update = False
633 if sub_opts:
634 for o, unused_a in sub_opts:
635 if o == '-f':
636 force_update = True
637 if not force_update and installed_version_string == latest_version_string:
638 self.CleanUpUpdateCommand(tf, dirs_to_remove)
639 raise CommandException('You have the latest version of gsutil installed.',
640 informational=True)
641
642 print(('This command will update to the "%s" version of\ngsutil at %s') %
643 (latest_version_string, self.gsutil_bin_dir))
644 self.ExplainIfSudoNeeded(tf, dirs_to_remove)
645
646 answer = raw_input('Proceed (Note: experimental command)? [y/N] ')
647 if not answer or answer.lower()[0] != 'y':
648 self.CleanUpUpdateCommand(tf, dirs_to_remove)
649 raise CommandException('Not running update.', informational=True)
650
651 # Ignore keyboard interrupts during the update to reduce the chance someone
652 # hitting ^C leaves gsutil in a broken state.
653 signal.signal(signal.SIGINT, signal.SIG_IGN)
654
655 # gsutil_bin_dir lists the path where the code should end up (like
656 # /usr/local/gsutil), which is one level down from the relative path in the
657 # tarball (since the latter creates files in ./gsutil). So, we need to
658 # extract at the parent directory level.
659 gsutil_bin_parent_dir = os.path.dirname(self.gsutil_bin_dir)
660
661 # Extract tarball to a temporary directory in a sibling to gsutil_bin_dir.
662 old_dir = tempfile.mkdtemp(dir=gsutil_bin_parent_dir)
663 new_dir = tempfile.mkdtemp(dir=gsutil_bin_parent_dir)
664 dirs_to_remove.append(old_dir)
665 dirs_to_remove.append(new_dir)
666 self.EnsureDirsSafeForUpdate(dirs_to_remove)
667 try:
668 tf.extractall(path=new_dir)
669 except Exception, e:
670 self.CleanUpUpdateCommand(tf, dirs_to_remove)
671 raise CommandException('Update failed: %s.' % e)
672
673 # Move old installation aside and new into place.
674 os.rename(self.gsutil_bin_dir, old_dir + os.sep + 'old')
675 os.rename(new_dir + os.sep + 'gsutil', self.gsutil_bin_dir)
676 self.CleanUpUpdateCommand(tf, dirs_to_remove)
677 signal.signal(signal.SIGINT, signal.SIG_DFL)
678 print 'Update complete.'
679
680 def CheckForDirFileConflict(self, src_uri, dst_path):
681 """Checks whether copying src_uri into dst_path is not possible.
682
683 This happens if a directory exists in local file system where a file
684 needs to go or vice versa. In that case we print an error message and
685 exits. Example: if the file "./x" exists and you try to do:
686 gsutil cp gs://mybucket/x/y .
687 the request can't succeed because it requires a directory where
688 the file x exists.
689
690 Args:
691 src_uri: source StorageUri of copy
692 dst_path: destination path.
693
694 Raises:
695 CommandException: if errors encountered.
696 """
697 final_dir = os.path.dirname(dst_path)
698 if os.path.isfile(final_dir):
699 raise CommandException('Cannot retrieve %s because it a file exists '
700 'where a directory needs to be created (%s).' %
701 (src_uri, final_dir))
702 if os.path.isdir(dst_path):
703 raise CommandException('Cannot retrieve %s because a directory exists '
704 '(%s) where the file needs to be created.' %
705 (src_uri, dst_path))
706
707 def GetAclCommand(self, args, unused_sub_opts=None, headers=None, debug=0):
708 """Implementation of getacl command.
709
710 Args:
711 args: command-line argument list.
712 unused_sub_opts: list of command-specific options from getopt.
713 headers: dictionary containing optional HTTP headers to pass to boto.
714 debug: debug level to pass in to boto connection (range 0..3).
715
716 Raises:
717 CommandException: if errors encountered.
718 """
719 # Wildcarding is allowed but must resolve to just one object.
720 uris = list(self.CmdWildcardIterator(args[0], headers=headers,
721 debug=debug))
722 if len(uris) != 1:
723 raise CommandException('Wildcards must resolve to exactly one object for '
724 '"getacl" command.')
725 uri = uris[0]
726 if not uri.bucket_name:
727 raise CommandException('"getacl" command must specify a bucket or '
728 'object.')
729 acl = uri.get_acl(False, headers)
730 # Pretty-print the XML to make it more easily human editable.
731 parsed_xml = xml.dom.minidom.parseString(acl.to_xml().encode('utf-8'))
732 print parsed_xml.toprettyxml(indent=' ')
733
734 class FileCopyCallbackHandler(object):
735 """Outputs progress info for large copy requests."""
736
737 def __init__(self, upload):
738 if upload:
739 self.announce_text = 'Uploading'
740 else:
741 self.announce_text = 'Downloading'
742
743 def call(self, total_bytes_transferred, total_size):
744 sys.stderr.write('%s: %s/%s \r' % (
745 self.announce_text,
746 MakeHumanReadable(total_bytes_transferred),
747 MakeHumanReadable(total_size)))
748 if total_bytes_transferred == total_size:
749 sys.stderr.write('\n')
750
751 def GetTransferHandlers(self, uri, key, file_size, upload):
752 """
753 Selects upload/download and callback handlers.
754
755 We use a callback handler that shows a simple textual progress indicator
756 if file_size is above the configurable threshold.
757
758 We use a resumable transfer handler if file_size is >= the configurable
759 threshold and resumable transfers are supported by the given provider.
760 boto supports resumable downloads for all providers, but resumable
761 uploads are currently only supported by GS.
762 """
763 config = boto.config
764 resumable_threshold = config.getint('GSUtil', 'resumable_threshold', ONE_MB)
765 if file_size >= resumable_threshold:
766 cb = self.FileCopyCallbackHandler(upload).call
767 num_cb = int(file_size / ONE_MB)
768 resumable_tracker_dir = config.get(
769 'GSUtil', 'resumable_tracker_dir',
770 os.path.expanduser('~' + os.sep + '.gsutil'))
771 if not os.path.exists(resumable_tracker_dir):
772 os.makedirs(resumable_tracker_dir)
773 if upload:
774 # Encode the src bucket and key into the tracker file name.
775 res_tracker_file_name = (
776 re.sub('[/\\\\]', '_', 'resumable_upload__%s__%s.url' %
777 (key.bucket.name, key.name)))
778 else:
779 # Encode the fully-qualified src file name into the tracker file name.
780 res_tracker_file_name = (
781 re.sub('[/\\\\]', '_', 'resumable_download__%s.etag' %
782 (os.path.realpath(uri.object_name))))
783 tracker_file = '%s%s%s' % (resumable_tracker_dir, os.sep,
784 res_tracker_file_name)
785 if upload:
786 if uri.scheme == 'gs':
787 transfer_handler = ResumableUploadHandler(tracker_file)
788 else:
789 transfer_handler = None
790 else:
791 transfer_handler = ResumableDownloadHandler(tracker_file)
792 else:
793 transfer_handler = None
794 cb = None
795 num_cb = None
796 return (cb, num_cb, transfer_handler)
797
798 def CopyObjToObjSameProvider(self, src_key, src_uri, dst_uri, headers):
799 # Do Object -> object copy within same provider (uses
800 # x-<provider>-copy-source metadata HTTP header to request copying at the
801 # server). (Note: boto does not currently provide a way to pass canned_acl
802 # when copying from object-to-object through x-<provider>-copy-source)
803 src_bucket = src_uri.get_bucket(False, headers)
804 dst_bucket = dst_uri.get_bucket(False, headers)
805 start_time = time.time()
806 dst_bucket.copy_key(dst_uri.object_name, src_bucket.name,
807 src_uri.object_name, headers)
808 end_time = time.time()
809 return (end_time - start_time, src_key.size)
810
811 def CheckFreeSpace(self, path):
812 """Return path/drive free space (in bytes)."""
813 if platform.system() == 'Windows':
814 free_bytes = ctypes.c_ulonglong(0)
815 ctypes.windll.kernel32.GetDiskFreeSpaceExW(ctypes.c_wchar_p(path), None,
816 None,
817 ctypes.pointer(free_bytes))
818 return free_bytes.value
819 else:
820 (_, f_frsize, _, _, f_bavail, _, _, _, _, _) = os.statvfs(path)
821 return f_frsize * f_bavail
822
823 def PerformResumableUploadIfApplies(self, fp, dst_uri, headers, canned_acl):
824 """
825 Performs resumable upload if supported by provider and file is above
826 threshold, else performs non-resumable upload.
827
828 Returns (elapsed_time, bytes_transferred).
829 """
830 start_time = time.time()
831 file_size = os.path.getsize(fp.name)
832 dst_key = dst_uri.new_key(False, headers)
833 (cb, num_cb, res_upload_handler) = self.GetTransferHandlers(
834 dst_uri, dst_key, file_size, True)
835 if dst_uri.scheme == 'gs':
836 # Resumable upload protocol is Google Storage-specific.
837 dst_key.set_contents_from_file(fp, headers, policy=canned_acl,
838 cb=cb, num_cb=num_cb,
839 res_upload_handler=res_upload_handler)
840 else:
841 dst_key.set_contents_from_file(fp, headers, policy=canned_acl,
842 cb=cb, num_cb=num_cb)
843 if res_upload_handler:
844 bytes_transferred = file_size - res_upload_handler.upload_start_point
845 else:
846 bytes_transferred = file_size
847 end_time = time.time()
848 return (end_time - start_time, bytes_transferred)
849
850 def UploadFileToObject(self, sub_opts, src_key, src_uri, dst_uri, headers,
851 debug):
852 gzip_exts = []
853 canned_acl = None
854 if sub_opts:
855 for o, a in sub_opts:
856 if o == '-a':
857 canned_acls = dst_uri.canned_acls()
858 if a not in canned_acls:
859 raise CommandException('Invalid canned ACL "%s".' % a)
860 canned_acl = a
861 elif o == '-t':
862 mimetype_tuple = mimetypes.guess_type(src_uri.object_name)
863 mime_type = mimetype_tuple[0]
864 content_encoding = mimetype_tuple[1]
865 if mime_type:
866 headers['Content-Type'] = mime_type
867 print '\t[Setting Content-Type=%s]' % mime_type
868 else:
869 print '\t[Unknown content type -> using application/octet stream]'
870 if content_encoding:
871 headers['Content-Encoding'] = content_encoding
872 elif o == '-z':
873 gzip_exts = a.split(',')
874 fname_parts = src_uri.object_name.split('.')
875 if len(fname_parts) > 1 and fname_parts[-1] in gzip_exts:
876 if debug:
877 print 'Compressing %s (to tmp)...' % src_key
878 gzip_tmp = tempfile.mkstemp()
879 gzip_path = gzip_tmp[1]
880 # Check for temp space. Assume the compressed object is at most 2x
881 # the size of the object (normally should compress to smaller than
882 # the object)
883 if self.CheckFreeSpace(gzip_path) < 2*int(os.path.getsize(src_key.name)):
884 raise CommandException('Inadequate temp space available to compress '
885 '%s' % src_key.name)
886 the_gzip = gzip.open(gzip_path, 'wb')
887 the_gzip.writelines(src_key.fp)
888 the_gzip.close()
889 headers['Content-Encoding'] = 'gzip'
890 (elapsed_time, bytes_transferred) = self.PerformResumableUploadIfApplies(
891 open(gzip_path, 'rb'), dst_uri, headers, canned_acl)
892 os.unlink(gzip_path)
893 else:
894 (elapsed_time, bytes_transferred) = self.PerformResumableUploadIfApplies(
895 src_key.fp, dst_uri, headers, canned_acl)
896 return (elapsed_time, bytes_transferred)
897
898 def DownloadObjectToFile(self, src_key, src_uri, dst_uri, headers, debug):
899 (cb, num_cb, res_download_handler) = self.GetTransferHandlers(
900 src_uri, src_key, src_key.size, False)
901 file_name = dst_uri.object_name
902 dir_name = os.path.dirname(file_name)
903 if dir_name and not os.path.exists(dir_name):
904 os.makedirs(dir_name)
905 # For gzipped objects not named *.gz download to a temp file and unzip.
906 if (hasattr(src_key, 'content_encoding') and
907 src_key.content_encoding == 'gzip' and
908 not file_name.endswith('.gz')):
909 # We can't use tempfile.mkstemp() here because we need a predictable
910 # filename for resumable downloads.
911 download_file_name = '%s_.gztmp' % file_name
912 need_to_unzip = True
913 else:
914 download_file_name = file_name
915 need_to_unzip = False
916 if res_download_handler:
917 fp = open(download_file_name, 'ab')
918 else:
919 fp = open(download_file_name, 'wb')
920 start_time = time.time()
921 src_key.get_contents_to_file(fp, headers, cb=cb, num_cb=num_cb,
922 res_download_handler=res_download_handler)
923 fp.close()
924 end_time = time.time()
925 if res_download_handler:
926 bytes_transferred = (
927 src_key.size - res_download_handler.download_start_point)
928 else:
929 bytes_transferred = src_key.size
930 if need_to_unzip:
931 if debug:
932 print 'Uncompressing tmp to %s...' % file_name
933 # Downloaded gzipped file to a filename w/o .gz extension, so unzip.
934 f_in = gzip.open(download_file_name, 'rb')
935 f_out = open(file_name, 'wb')
936 f_out.writelines(f_in)
937 f_out.close();
938 f_in.close();
939 os.unlink(download_file_name)
940 return (end_time - start_time, bytes_transferred)
941
942 def CopyFileToFile(self, src_key, dst_uri, headers):
943 dst_key = dst_uri.new_key(False, headers)
944 start_time = time.time()
945 dst_key.set_contents_from_file(src_key.fp, headers)
946 end_time = time.time()
947 return (end_time - start_time, os.path.getsize(src_key.fp.name))
948
949 def CopyObjToObjDiffProvider(self, sub_opts, src_key, src_uri, dst_uri,
950 headers, debug):
951 # We implement cross-provider object copy through a local temp file.
952 # Note that a downside of this approach is that killing the gsutil
953 # process partway through and then restarting will always repeat the
954 # download and upload, because the temp file name is different for each
955 # incarnation. (If however you just leave the process running and failures
956 # happen along the way, they will continue to restart and make progress
957 # as long as not too many failures happen in a row with no progress.)
958 tmp = tempfile.NamedTemporaryFile()
959 if self.CheckFreeSpace(tempfile.tempdir) < src_key.size:
960 raise CommandException('Inadequate temp space available to perform the '
961 'requested copy')
962 start_time = time.time()
963 file_uri = self.StorageUri('file://%s' % tmp.name, debug=debug,
964 validate=False)
965 try:
966 self.DownloadObjectToFile(src_key, src_uri, file_uri, headers, debug)
967 self.UploadFileToObject(sub_opts, file_uri.get_key(), file_uri, dst_uri,
968 headers, debug)
969 finally:
970 tmp.close()
971 end_time = time.time()
972 return (end_time - start_time, src_key.size)
973
974 def PerformCopy(self, src_uri, dst_uri, sub_opts=None, headers=None, debug=0):
975 """Helper method for CopyObjsCommand.
976
977 Args:
978 src_uri: source StorageUri.
979 dst_uri: destination StorageUri.
980 sub_opts: list of command-specific options from getopt.
981 headers: dictionary containing optional HTTP headers to pass to boto.
982 debug: debug level to pass in to boto connection (range 0..3).
983
984 Returns:
985 (elapsed_time, bytes_transferred) excluding overhead like initial HEAD.
986
987 Raises:
988 CommandException: if errors encountered.
989 """
990 # Make a copy of the input headers each time so we can set a different
991 # MIME type for each object.
992 if headers:
993 headers = headers.copy()
994 else:
995 headers = {}
996
997 src_key = src_uri.get_key(False, headers)
998 if not src_key:
999 raise CommandException('"%s" does not exist.' % src_uri)
1000
1001 # Separately handle cases to avoid extra file and network copying of
1002 # potentially very large files/objects.
1003
1004 if src_uri.is_cloud_uri() and dst_uri.is_cloud_uri():
1005 if src_uri.scheme == dst_uri.scheme:
1006 return self.CopyObjToObjSameProvider(src_key, src_uri, dst_uri,
1007 headers)
1008 else:
1009 return self.CopyObjToObjDiffProvider(sub_opts, src_key, src_uri,
1010 dst_uri, headers, debug)
1011 elif src_uri.is_file_uri() and dst_uri.is_cloud_uri():
1012 return self.UploadFileToObject(sub_opts, src_key, src_uri, dst_uri,
1013 headers, debug)
1014 elif src_uri.is_cloud_uri() and dst_uri.is_file_uri():
1015 return self.DownloadObjectToFile(src_key, src_uri, dst_uri, headers,
1016 debug)
1017 elif src_uri.is_file_uri() and dst_uri.is_file_uri():
1018 return self.CopyFileToFile(src_key, dst_uri, headers)
1019 else:
1020 raise CommandException('Unexpected src/dest case')
1021
1022 def ExpandWildcardsAndContainers(self, uri_strs, sub_opts=None, headers=None,
1023 debug=0):
1024 """Expands URI wildcarding, object-less bucket names, and directory names.
1025
1026 Examples:
1027 Calling with uri_strs='gs://bucket' will enumerate all contained objects.
1028 Calling with uri_strs='file:///tmp' will enumerate all files under /tmp
1029 (or under any subdirectory).
1030 The previous example is equivalent to uri_strs='file:///tmp/*'
1031 and to uri_strs='file:///tmp/**'
1032
1033 Args:
1034 uri_strs: URI strings needing expansion
1035 sub_opts: list of command-specific options from getopt.
1036 headers: dictionary containing optional HTTP headers to pass to boto.
1037 debug: debug level to pass in to boto connection (range 0..3).
1038
1039 Returns:
1040 dict mapping StorageUri -> list of StorageUri, for each input uri_str.
1041
1042 We build a dict of the expansion instead of using a generator to
1043 iterate incrementally because caller needs to know count before
1044 iterating and performing copy operations.
1045 """
1046 # The algorithm we use is:
1047 # 1. Build a first level expanded list from uri_strs consisting of all
1048 # URIs that aren't file wildcards, plus expansions of the file wildcards.
1049 # 2. Build dict from above expanded list.
1050 # We do so that we can properly handle the following example:
1051 # gsutil cp file0 dir0 gs://bucket
1052 # where dir0 contains file1 and dir1/file2.
1053 # If we didn't do the first expansion, this cp command would end up
1054 # with this expansion:
1055 # {file://file0:[file://file0],file://dir0:[file://dir0/file1,
1056 # file://dir0/dir1/file2]}
1057 # instead of the (correct) expansion:
1058 # {file://file0:[file://file0],file://dir0/file1:[file://dir0/file1],
1059 # file://dir0/dir1:[file://dir0/dir1/file2]}
1060 # The latter expansion is needed so that in the "Copying..." loop of
1061 # CopyObjsCommand we know that dir0 was being copied, so we create an
1062 # object called gs://bucket/dir0/dir1/file2. (Otherwise it would look
1063 # like a single file was being copied, so we'd create an object called
1064 # gs://bucket/file2.)
1065
1066 should_recurse = False
1067 if sub_opts:
1068 for o, unused_a in sub_opts:
1069 if o == '-r' or o == '-R':
1070 should_recurse = True
1071
1072 # Step 1.
1073 uris_to_expand = []
1074 for uri_str in uri_strs:
1075 uri = self.StorageUri(uri_str, debug=debug, validate=False)
1076 if uri.is_file_uri() and ContainsWildcard(uri_str):
1077 uris_to_expand.extend(list(
1078 self.CmdWildcardIterator(uri, headers=headers, debug=debug)))
1079 else:
1080 uris_to_expand.append(uri)
1081
1082 # Step 2.
1083 result = {}
1084 for uri in uris_to_expand:
1085 if uri.names_container():
1086 if not should_recurse:
1087 if uri.is_file_uri():
1088 desc = 'directory'
1089 else:
1090 desc = 'bucket'
1091 print 'Omitting %s "%s".' % (desc, uri.uri)
1092 result[uri] = []
1093 continue
1094 if uri.is_file_uri():
1095 # dir -> convert to implicit recursive wildcard.
1096 uri_to_iter = '%s/**' % uri.uri
1097 else:
1098 # bucket -> convert to implicit wildcard.
1099 uri_to_iter = uri.clone_replace_name('*')
1100 else:
1101 uri_to_iter = uri
1102 result[uri] = list(self.CmdWildcardIterator(
1103 uri_to_iter, headers=headers, debug=debug))
1104 return result
1105
1106 def ErrorCheckCopyRequest(self, src_uri_expansion, dst_uri_str, headers,
1107 debug, command='cp'):
1108 """Checks copy request for problems, and builds needed base_dst_uri.
1109
1110 base_dst_uri is the base uri to be used if it's a multi-object copy, e.g.,
1111 the URI for the destination bucket. The actual dst_uri can then be
1112 constructed from the src_uri and this base_dst_uri.
1113
1114 Args:
1115 src_uri_expansion: result from ExpandWildcardsAndContainers call.
1116 dst_uri_str: string representation of destination StorageUri.
1117 headers: dictionary containing optional HTTP headers to pass to boto.
1118 debug: flag indicating whether to include debug output
1119 command: name of command on behalf of which this call is running.
1120
1121 Returns:
1122 (base_dst_uri to use for copy, bool indicator of multi-source request).
1123
1124 Raises:
1125 CommandException: if errors found.
1126 """
1127 for src_uri in src_uri_expansion:
1128 if src_uri.is_cloud_uri() and not src_uri.bucket_name:
1129 raise CommandException('Provider-only src_uri (%s)')
1130
1131 if ContainsWildcard(dst_uri_str):
1132 matches = list(self.CmdWildcardIterator(dst_uri_str, headers=headers,
1133 debug=debug))
1134 if len(matches) > 1:
1135 raise CommandException('Destination (%s) matches more than 1 URI' %
1136 dst_uri_str)
1137 base_dst_uri = matches[0]
1138 else:
1139 base_dst_uri = self.StorageUri(dst_uri_str, debug=debug)
1140
1141 # Make sure entire expansion didn't result in nothing to copy. This can
1142 # happen if user request copying a directory w/o -r option, for example.
1143 have_work = False
1144 for v in src_uri_expansion.values():
1145 if v:
1146 have_work = True
1147 break
1148 if not have_work:
1149 raise CommandException('Nothing to copy')
1150
1151 # If multi-object copy request ensure base_dst_uri names a container.
1152 multi_src_request = (len(src_uri_expansion) > 1 or
1153 len(src_uri_expansion.values()[0]) > 1)
1154 if multi_src_request:
1155 self.InsistUriNamesContainer(command, base_dst_uri)
1156
1157 # Ensure no src/dest pairs would overwrite src. Note that this is
1158 # more restrictive than the UNIX 'cp' command (which would, for example,
1159 # allow "mv * dir" and just skip the implied mv dir dir). We disallow such
1160 # partial completion operations in cloud copies because they are risky.
1161 for src_uri in iter(src_uri_expansion):
1162 for exp_src_uri in src_uri_expansion[src_uri]:
1163 new_dst_uri = self.ConstructDstUri(src_uri, exp_src_uri, base_dst_uri)
1164 if self.SrcDstSame(exp_src_uri, new_dst_uri):
1165 raise CommandException('cp: "%s" and "%s" are the same object - '
1166 'abort.' % (exp_src_uri.uri, new_dst_uri.uri))
1167
1168 return (base_dst_uri, multi_src_request)
1169
1170 def HandleMultiSrcCopyRequst(self, src_uri_expansion, dst_uri):
1171 """
1172 Rewrites dst_uri and creates dest dir as needed, if this is a
1173 multi-source copy.
1174
1175 Args:
1176 src_uri_expansion: result from ExpandWildcardsAndContainers call.
1177 dst_uri: uri constructed by ErrorCheckCopyRequest() call.
1178
1179 Returns:
1180 dst_uri to use for copy.
1181 """
1182 # If src_uri and dst_uri both name containers, handle
1183 # two cases to make copy command work like UNIX "cp -r" works:
1184 # a) if dst_uri names a non-existent directory, copy objects to a new
1185 # directory with the dst_uri name. In this case,
1186 # gsutil gs://bucket/a dir
1187 # should create dir/a.
1188 # b) if dst_uri names an existing directory, copy objects under that
1189 # directory. In this case,
1190 # gsutil gs://bucket/a dir
1191 # should create dir/bucket/a.
1192 src_uri_to_check = src_uri_expansion.keys()[0]
1193 if (src_uri_to_check.names_container() and dst_uri.names_container() and
1194 os.path.exists(dst_uri.object_name)):
1195 new_name = ('%s%s%s' % (dst_uri.object_name, os.sep,
1196 src_uri_to_check.bucket_name)).rstrip('/')
1197 dst_uri = dst_uri.clone_replace_name(new_name)
1198 # Create dest directory if needed.
1199 if dst_uri.is_file_uri() and not os.path.exists(dst_uri.object_name):
1200 os.makedirs(dst_uri.object_name)
1201 return dst_uri
1202
1203 def SrcDstSame(self, src_uri, dst_uri):
1204 """Checks if src_uri and dst_uri represent same object.
1205
1206 We don't handle anything about hard or symbolic links.
1207
1208 Args:
1209 src_uri: source StorageUri.
1210 dst_uri: dest StorageUri.
1211
1212 Returns:
1213 Bool indication.
1214 """
1215 if src_uri.is_file_uri() and dst_uri.is_file_uri():
1216 # Translate a/b/./c to a/b/c, so src=dst comparison below works.
1217 new_src_path = re.sub('%s+\.%s+' % (os.sep, os.sep), os.sep,
1218 src_uri.object_name)
1219 new_src_path = re.sub('^.%s+' % os.sep, '', new_src_path)
1220 new_dst_path = re.sub('%s+\.%s+' % (os.sep, os.sep), os.sep,
1221 dst_uri.object_name)
1222 new_dst_path = re.sub('^.%s+' % os.sep, '', new_dst_path)
1223 return (src_uri.clone_replace_name(new_src_path).uri ==
1224 dst_uri.clone_replace_name(new_dst_path).uri)
1225 else:
1226 return src_uri.uri == dst_uri.uri
1227
1228 def ConstructDstUri(self, src_uri, exp_src_uri, base_dst_uri):
1229 """Constructs a destination URI for CopyObjsCommand.
1230
1231 Args:
1232 src_uri: src_uri to be copied.
1233 exp_src_uri: single URI from wildcard expansion of src_uri.
1234 base_dst_uri: uri constructed by ErrorCheckCopyRequest() call.
1235
1236 Returns:
1237 dst_uri to use for copy.
1238 """
1239 if base_dst_uri.names_container():
1240 # To match naming semantics of UNIX 'cp' command, copying files
1241 # to buckets/dirs should result in objects/files named by just the
1242 # final filename component; while copying directories should result
1243 # in objects/files mirroring the directory hierarchy. Example of the
1244 # first case:
1245 # gsutil cp dir1/file1 gs://bucket
1246 # should create object gs://bucket/file1
1247 # Example of the second case:
1248 # gsutil cp dir1/dir2 gs://bucket
1249 # should create object gs://bucket/dir2/file2 (assuming dir1/dir2
1250 # contains file2).
1251 if src_uri.names_container():
1252 dst_path_start = (src_uri.object_name.rstrip(os.sep)
1253 .rpartition(os.sep)[-1])
1254 start_pos = exp_src_uri.object_name.find(dst_path_start)
1255 dst_key_name = exp_src_uri.object_name[start_pos:]
1256 else:
1257 # src is a file or object, so use final component of src name.
1258 dst_key_name = os.path.basename(exp_src_uri.object_name)
1259 if base_dst_uri.is_file_uri():
1260 # dst names a directory, so append src obj name to dst obj name.
1261 dst_key_name = '%s%s%s' % (base_dst_uri.object_name, os.sep,
1262 dst_key_name)
1263 self.CheckForDirFileConflict(exp_src_uri, dst_key_name)
1264 else:
1265 # dest is an object or file: use dst obj name
1266 dst_key_name = base_dst_uri.object_name
1267 return base_dst_uri.clone_replace_name(dst_key_name)
1268
1269 def CopyObjsCommand(self, args, sub_opts=None, headers=None, debug=0,
1270 command='cp'):
1271 """Implementation of cp command.
1272
1273 Args:
1274 args: command-line argument list.
1275 sub_opts: list of command-specific options from getopt.
1276 headers: dictionary containing optional HTTP headers to pass to boto.
1277 debug: debug level to pass in to boto connection (range 0..3).
1278 command: name of command on behalf of which this call is running.
1279
1280 Raises:
1281 CommandException: if errors encountered.
1282 """
1283 # Expand wildcards and containers in source StorageUris.
1284 src_uri_expansion = self.ExpandWildcardsAndContainers(
1285 args[0:len(args)-1], sub_opts, headers, debug)
1286
1287 # Check for various problems and determine base_dst_uri based for request.
1288 (base_dst_uri, multi_src_request) = self.ErrorCheckCopyRequest(
1289 src_uri_expansion, args[-1], headers, debug, command)
1290 # Rewrite base_dst_uri and create dest dir as needed for multi-source copy.
1291 if multi_src_request:
1292 base_dst_uri = self.HandleMultiSrcCopyRequst(src_uri_expansion,
1293 base_dst_uri)
1294
1295 # Now iterate over expanded src URIs, and perform copy operations.
1296 total_elapsed_time = total_bytes_transferred = 0
1297 for src_uri in iter(src_uri_expansion):
1298 for exp_src_uri in src_uri_expansion[src_uri]:
1299 print 'Copying %s...' % exp_src_uri
1300 dst_uri = self.ConstructDstUri(src_uri, exp_src_uri, base_dst_uri)
1301 (elapsed_time, bytes_transferred) = self.PerformCopy(
1302 exp_src_uri, dst_uri, sub_opts, headers, debug)
1303 total_elapsed_time += elapsed_time
1304 total_bytes_transferred += bytes_transferred
1305 if debug == 3:
1306 # Note that this only counts the actual GET and PUT bytes for the copy
1307 # - not any transfers for doing wildcard expansion, the initial HEAD
1308 # request boto performs when doing a bucket.get_key() operation, etc.
1309 if total_bytes_transferred != 0:
1310 print 'Total bytes copied=%d, total elapsed time=%5.3f secs (%sps)' % (
1311 total_bytes_transferred, total_elapsed_time,
1312 MakeHumanReadable(float(total_bytes_transferred) /
1313 float(total_elapsed_time)))
1314
1315 def HelpCommand(self, unused_args, unused_sub_opts=None, unused_headers=None,
1316 unused_debug=None):
1317 """Implementation of help command.
1318
1319 Args:
1320 unused_args: command-line argument list.
1321 unused_sub_opts: list of command-specific options from getopt.
1322 unused_headers: dictionary containing optional HTTP headers to send.
1323 unused_debug: flag indicating whether to include debug output.
1324 """
1325 self.OutputUsageAndExit()
1326
1327 def VerCommand(self, unused_args, unused_sub_opts=None, unused_headers=None,
1328 unused_debug=None):
1329 """Implementation of ver command.
1330
1331 Args:
1332 unused_args: command-line argument list.
1333 unused_sub_opts: list of command-specific options from getopt.
1334 unused_headers: dictionary containing optional HTTP headers to send.
1335 unused_debug: flag indicating whether to include debug output.
1336 """
1337 config_ver = ''
1338 for path in BotoConfigLocations:
1339 try:
1340 f = open(path, 'r')
1341 while True:
1342 line = f.readline()
1343 if not line:
1344 break
1345 if line.find('was created by gsutil version') != -1:
1346 config_ver = ', config file version %s' % line.split('"')[-2]
1347 break
1348 # Only look at first first config file found in BotoConfigLocations.
1349 break
1350 except IOError:
1351 pass
1352
1353 print 'gsutil version %s%s, python version %s' % (
1354 self.LoadVersionString(), config_ver, sys.version)
1355
1356 def PrintBucketInfo(self, bucket_uri, listing_style, headers=None, debug=0):
1357 """Print listing info for given bucket.
1358
1359 Args:
1360 bucket_uri: StorageUri being listed.
1361 listing_style: ListingStyle enum describing type of output desired.
1362 headers: dictionary containing optional HTTP headers to pass to boto.
1363 debug: debug level to pass in to boto connection (range 0..3).
1364
1365 Returns:
1366 Tuple (total objects, total bytes) in the bucket.
1367 """
1368 bucket_objs = 0
1369 bucket_bytes = 0
1370 if listing_style == ListingStyle.SHORT:
1371 print bucket_uri
1372 else:
1373 try:
1374 for obj in self.CmdWildcardIterator(
1375 bucket_uri.clone_replace_name('*'),
1376 ResultType.KEYS, headers=headers, debug=debug):
1377 bucket_objs += 1
1378 bucket_bytes += obj.size
1379 except WildcardException, e:
1380 # Ignore non-matching wildcards, to allow empty bucket listings.
1381 if e.reason.find('No matches') == -1:
1382 raise e
1383 if listing_style == ListingStyle.LONG:
1384 print '%s : %s objects, %s' % (
1385 bucket_uri, bucket_objs, MakeHumanReadable(bucket_bytes))
1386 else: # listing_style == ListingStyle.LONG_LONG:
1387 location_constraint = bucket_uri.get_location(validate=False,
1388 headers=headers)
1389 location_output = ''
1390 if location_constraint:
1391 location_output = '\n\tLocationConstraint: %s' % location_constraint
1392 self.proj_id_handler.FillInProjectHeaderIfNeeded(
1393 'get_acl', bucket_uri, headers)
1394 print '%s :\n\t%d objects, %s%s\n\tACL: %s' % (
1395 bucket_uri, bucket_objs, MakeHumanReadable(bucket_bytes),
1396 location_output, bucket_uri.get_acl(False, headers))
1397 return (bucket_objs, bucket_bytes)
1398
1399 def PrintObjectInfo(self, iterated_uri, obj, listing_style, headers, debug):
1400 """Print listing info for given object.
1401
1402 Args:
1403 iterated_uri: base StorageUri being listed (e.g., gs://abc/*).
1404 obj: object to be listed (or None if no associated object).
1405 listing_style: ListingStyle enum describing type of output desired.
1406 headers: dictionary containing optional HTTP headers to pass to boto.
1407 debug: flag indicating whether to include debug output
1408
1409 Returns:
1410 Object length (if listing_style is one of the long listing formats).
1411
1412 Raises:
1413 Exception: if calling bug encountered.
1414 """
1415 if listing_style == ListingStyle.SHORT:
1416 print UriStrFor(iterated_uri, obj)
1417 return 0
1418 elif listing_style == ListingStyle.LONG:
1419 # Exclude timestamp fractional secs (example: 2010-08-23T12:46:54.187Z).
1420 timestamp = obj.last_modified[:19].decode('utf8').encode('ascii')
1421 print '%10s %s %s' % (obj.size, timestamp, UriStrFor(iterated_uri, obj))
1422 return obj.size
1423 elif listing_style == ListingStyle.LONG_LONG:
1424 uri_str = UriStrFor(iterated_uri, obj)
1425 print '%s:' % uri_str
1426 obj.open_read()
1427 print '\tObject size:\t%s' % obj.size
1428 print '\tLast mod:\t%s' % obj.last_modified
1429 if obj.cache_control:
1430 print '\tCache control:\t%s' % obj.cache_control
1431 print '\tMIME type:\t%s' % obj.content_type
1432 if obj.content_encoding:
1433 print '\tContent-Encoding:\t%s' % obj.content_encoding
1434 if obj.metadata:
1435 for name in obj.metadata:
1436 print '\tMetadata:\t%s = %s' % (name, obj.metadata[name])
1437 print '\tEtag:\t%s' % obj.etag.strip('"\'')
1438 print '\tACL:\t%s' % (
1439 self.StorageUri(uri_str, debug=debug).get_acl(False, headers))
1440 return obj.size
1441 else:
1442 raise Exception('Unexpected ListingStyle(%s)' % listing_style)
1443
1444 def ListCommand(self, args, sub_opts=None, headers=None, debug=0):
1445 """Implementation of ls command.
1446
1447 Args:
1448 args: command-line argument list.
1449 sub_opts: list of command-specific options from getopt.
1450 headers: dictionary containing optional HTTP headers to pass to boto.
1451 debug: debug level to pass in to boto connection (range 0..3).
1452 """
1453 listing_style = ListingStyle.SHORT
1454 get_bucket_info = False
1455 if sub_opts:
1456 for o, a in sub_opts:
1457 if o == '-b':
1458 get_bucket_info = True
1459 elif o == '-l':
1460 listing_style = ListingStyle.LONG
1461 elif o == '-L':
1462 listing_style = ListingStyle.LONG_LONG
1463 elif o == '-p':
1464 self.proj_id_handler.SetProjectId(a)
1465
1466 if not args:
1467 # default to listing all gs buckets
1468 args = ['gs://']
1469
1470 total_objs = 0
1471 total_bytes = 0
1472 for uri_str in args:
1473 uri = self.StorageUri(uri_str, debug=debug, validate=False)
1474 self.proj_id_handler.FillInProjectHeaderIfNeeded('ls', uri, headers)
1475
1476 if not uri.bucket_name:
1477 # Provider URI: add bucket wildcard to list buckets.
1478 for uri in self.CmdWildcardIterator('%s://*' % uri.scheme,
1479 headers=headers, debug=debug):
1480 (bucket_objs, bucket_bytes) = self.PrintBucketInfo(uri, listing_style,
1481 headers=headers,
1482 debug=debug)
1483 total_bytes += bucket_bytes
1484 total_objs += bucket_objs
1485
1486 elif not uri.object_name:
1487 if get_bucket_info:
1488 # ls -b request on provider+bucket URI: List info about bucket(s).
1489 for uri in self.CmdWildcardIterator(uri, headers=headers,
1490 debug=debug):
1491 (bucket_objs, bucket_bytes) = self.PrintBucketInfo(uri,
1492 listing_style,
1493 headers=headers,
1494 debug=debug)
1495 total_bytes += bucket_bytes
1496 total_objs += bucket_objs
1497 else:
1498 # ls request on provider+bucket URI: List objects in the bucket(s).
1499 for obj in self.CmdWildcardIterator(uri.clone_replace_name('*'),
1500 ResultType.KEYS, headers=headers,
1501 debug=debug):
1502 total_bytes += self.PrintObjectInfo(uri, obj, listing_style,
1503 headers=headers, debug=debug)
1504 total_objs += 1
1505
1506 else:
1507 # Provider+bucket+object URI -> list the object(s).
1508 for obj in self.CmdWildcardIterator(uri, ResultType.KEYS,
1509 headers=headers, debug=debug):
1510 total_bytes += self.PrintObjectInfo(uri, obj, listing_style,
1511 headers=headers, debug=debug)
1512 total_objs += 1
1513 if listing_style != ListingStyle.SHORT:
1514 print ('TOTAL: %d objects, %d bytes (%s)' %
1515 (total_objs, total_bytes, MakeHumanReadable(float(total_bytes))))
1516
1517 def MakeBucketsCommand(self, args, sub_opts=None, headers=None, debug=0):
1518 """Implementation of mb command.
1519
1520 Args:
1521 args: command-line argument list.
1522 sub_opts: list of command-specific options from getopt.
1523 headers: dictionary containing optional HTTP headers to pass to boto.
1524 debug: debug level to pass in to boto connection (range 0..3).
1525
1526 Raises:
1527 CommandException: if errors encountered.
1528 """
1529 location = ''
1530 if sub_opts:
1531 for o, a in sub_opts:
1532 if o == '-l':
1533 location = a
1534 elif o == '-p':
1535 self.proj_id_handler.SetProjectId(a)
1536
1537 if not headers:
1538 headers = {}
1539 else:
1540 headers = headers.copy()
1541
1542 for bucket_uri_str in args:
1543 bucket_uri = self.StorageUri(bucket_uri_str, debug=debug)
1544 self.proj_id_handler.FillInProjectHeaderIfNeeded('mb', bucket_uri, headers )
1545 print 'Creating %s...' % bucket_uri
1546 bucket_uri.create_bucket(headers=headers, location=location)
1547
1548 def MoveObjsCommand(self, args, sub_opts=None, headers=None, debug=0):
1549 """Implementation of mv command.
1550
1551 Note that there is no atomic rename operation - this command is simply
1552 a shorthand for 'cp' followed by 'rm'.
1553
1554 Args:
1555 args: command-line argument list.
1556 sub_opts: list of command-specific options from getopt.
1557 headers: dictionary containing optional HTTP headers to pass to boto.
1558 debug: debug level to pass in to boto connection (range 0..3).
1559
1560 Raises:
1561 CommandException: if errors encountered.
1562 """
1563 # Refuse to delete a bucket or directory src URI (force users to explicitly
1564 # do that as a separate operation).
1565 src_uri_to_check = self.StorageUri(args[0], debug=debug, validate=False)
1566 if src_uri_to_check.names_container():
1567 raise CommandException('Will not remove source buckets or directories. '
1568 'You must separately copy and remove for that '
1569 'purpose.')
1570
1571 if len(args) > 2:
1572 self.InsistUriNamesContainer('mv', self.StorageUri(args[-1]))
1573
1574 # Expand wildcards before calling CopyObjsCommand and RemoveObjsCommand,
1575 # to prevent the following problem: starting with a bucket containing
1576 # only the object gs://bucket/obj, say the user does:
1577 # gsutil mv gs://bucket/* gs://bucket/d.txt
1578 # If we didn't expand the wildcard first, the CopyObjsCommand would
1579 # first copy gs://bucket/obj to gs://bucket/d.txt, and the
1580 # RemoveObjsCommand would then remove that object.
1581 exp_arg_list = []
1582 for uri_str in args:
1583 uri = self.StorageUri(uri_str, debug=debug, validate=False)
1584 if ContainsWildcard(uri_str):
1585 exp_arg_list.extend(str(u) for u in list(
1586 self.CmdWildcardIterator(uri, headers=headers, debug=debug)))
1587 else:
1588 exp_arg_list.append(uri.uri)
1589
1590 self.CopyObjsCommand(exp_arg_list, sub_opts, headers, debug, 'mv')
1591 self.RemoveObjsCommand(exp_arg_list[0:-1], sub_opts, headers, debug)
1592
1593 def RemoveBucketsCommand(self, args, unused_sub_opts=None, headers=None,
1594 debug=0):
1595 """Implementation of rb command.
1596
1597 Args:
1598 args: command-line argument list.
1599 unused_sub_opts: list of command-specific options from getopt.
1600 headers: dictionary containing optional HTTP headers to pass to boto.
1601 debug: debug level to pass in to boto connection (range 0..3).
1602
1603 Raises:
1604 CommandException: if errors encountered.
1605 """
1606 # Expand bucket name wildcards, if any.
1607 for uri_str in args:
1608 for uri in self.CmdWildcardIterator(uri_str, headers=headers,
1609 debug=debug):
1610 if uri.object_name:
1611 raise CommandException('"rb" command requires a URI with no object '
1612 'name')
1613 print 'Removing %s...' % uri
1614 uri.delete_bucket(headers)
1615
1616 def RemoveObjsCommand(self, args, sub_opts=None, headers=None, debug=0):
1617 """Implementation of rm command.
1618
1619 Args:
1620 args: command-line argument list.
1621 sub_opts: list of command-specific options from getopt.
1622 headers: dictionary containing optional HTTP headers to pass to boto.
1623 debug: debug level to pass in to boto connection (range 0..3).
1624
1625 Raises:
1626 CommandException: if errors encountered.
1627 """
1628 continue_on_error = False
1629 if sub_opts:
1630 for o, unused_a in sub_opts:
1631 if o == '-f':
1632 continue_on_error = True
1633 # Expand object name wildcards, if any.
1634 for uri_str in args:
1635 try:
1636 for uri in self.CmdWildcardIterator(uri_str, headers=headers,
1637 debug=debug):
1638 if uri.names_container():
1639 if uri.is_cloud_uri():
1640 # Before offering advice about how to do rm + rb, ensure those
1641 # commands won't fail because of bucket naming problems.
1642 boto.s3.connection.check_lowercase_bucketname(uri.bucket_name)
1643 uri_str = uri_str.rstrip('/\\')
1644 raise CommandException('"rm" command will not remove buckets. To '
1645 'delete this/these bucket(s) do:\n\tgsutil rm '
1646 '%s/*\n\tgsutil rb %s' % (uri_str, uri_str))
1647 print 'Removing %s...' % uri
1648 uri.delete_key(validate=False, headers=headers)
1649 except Exception, e:
1650 if not continue_on_error:
1651 raise
1652
1653 def WriteBotoConfigFile(self, config_file, use_oauth2=True,
1654 launch_browser=True, oauth2_scopes=[SCOPE_FULL_CONTROL]):
1655 """Creates a boto config file interactively.
1656
1657 Needed credentials are obtained interactively, either by asking the user for
1658 access key and secret, or by walking the user through the OAuth2 approval
1659 flow.
1660
1661 Args:
1662 config_file: file object to which the resulting config file will be
1663 written.
1664 use_oauth2: if True, walk user through OAuth2 approval flow and produce a
1665 config with an oauth2_refresh_token credential. If false, ask the
1666 user for access key and secret.
1667 launch_browser: in the OAuth2 approval flow, attempt to open a browser
1668 window and navigate to the approval URL.
1669 oauth2_scopes: a list of OAuth2 scopes to request authorization for, when
1670 using OAuth2.
1671 """
1672
1673 # Collect credentials
1674 provider_map = {'aws': 'aws', 'google': 'gs'}
1675 uri_map = {'aws': 's3', 'google': 'gs'}
1676 key_ids = {}
1677 sec_keys = {}
1678 if use_oauth2:
1679 oauth2_refresh_token = oauth2_helper.OAuth2ApprovalFlow(
1680 oauth2_helper.OAuth2ClientFromBotoConfig(boto.config),
1681 oauth2_scopes, launch_browser)
1682 else:
1683 got_creds = False
1684 for provider in provider_map:
1685 if provider == 'google':
1686 key_ids[provider] = raw_input('What is your %s access key ID? ' %
1687 provider)
1688 sec_keys[provider] = raw_input('What is your %s secret access key? ' %
1689 provider)
1690 got_creds = True
1691 if not key_ids[provider] or not sec_keys[provider]:
1692 raise CommandException(
1693 'Incomplete credentials provided. Please try again.')
1694 if not got_creds:
1695 raise CommandException('No credentials provided. Please try again.')
1696
1697 # Write the config file prelude.
1698 config_file.write(CONFIG_PRELUDE_CONTENT)
1699 config_file.write(
1700 '# This file was created by gsutil version "%s"\n# at %s.\n'
1701 % (self.LoadVersionString(),
1702 datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')))
1703 config_file.write('#\n# You can create additional configuration files by '
1704 'running\n# gsutil config [options] [-o <config-file>]\n\n\n')
1705
1706 # Write the config file Credentials section.
1707 config_file.write('[Credentials]\n\n')
1708 if use_oauth2:
1709 config_file.write('# Google OAuth2 credentials (for "gs://" URIs):\n')
1710 config_file.write('# The following OAuth2 token is authorized for '
1711 'scope(s):\n')
1712 for scope in oauth2_scopes:
1713 config_file.write('# %s\n' % scope)
1714 config_file.write('gs_oauth2_refresh_token = %s\n\n' %
1715 oauth2_refresh_token.refresh_token)
1716 else:
1717 config_file.write('# To add Google OAuth2 credentials ("gs://" URIs), '
1718 'edit and uncomment the\n# following line:\n'
1719 '#gs_oauth2_refresh_token = <your OAuth2 refresh token>\n\n')
1720
1721 for provider in provider_map:
1722 key_prefix = provider_map[provider]
1723 uri_scheme = uri_map[provider]
1724 if provider in key_ids and provider in sec_keys:
1725 config_file.write('# %s credentials ("%s://" URIs):\n' %
1726 (provider, uri_scheme))
1727 config_file.write('%s_access_key_id = %s\n' %
1728 (key_prefix, key_ids[provider]))
1729 config_file.write('%s_secret_access_key = %s\n' %
1730 (key_prefix, sec_keys[provider]))
1731 else:
1732 config_file.write('# To add %s credentials ("%s://" URIs), edit and '
1733 'uncomment the\n# following two lines:\n'
1734 '#%s_access_key_id = <your %s access key ID>\n'
1735 '#%s_secret_access_key = <your %s secret access key>\n' %
1736 (provider, uri_scheme, key_prefix, provider, key_prefix,
1737 provider))
1738 host_key = Provider.HostKeyMap[provider]
1739 config_file.write('# The ability to specify an alternate storage host '
1740 'is primarily for cloud\n# storage service developers.\n'
1741 '#%s_host = <alternate storage host address>\n\n' % host_key)
1742
1743 # Write the config file Boto section.
1744 config_file.write('%s\n' % CONFIG_BOTO_SECTION_CONTENT)
1745
1746 # Write the config file GSUtil section that doesn't depend on user input.
1747 config_file.write(CONFIG_INPUTLESS_GSUTIL_SECTION_CONTENT)
1748
1749 # Write the config file GSUtil section that includes the default
1750 # project ID input from the user.
1751 if launch_browser:
1752 sys.stdout.write(
1753 'Attempting to launch a browser to open the Google API console at '
1754 'URL: %s\n\n'
1755 '[Note: due to a Python bug, you may see a spurious error message '
1756 '"object is not\n callable [...] in [...] Popen.__del__" which can '
1757 'be ignored.]\n\n' % GOOG_API_CONSOLE_URI)
1758 sys.stdout.write(
1759 'In your browser you should see the API Console. Click "Storage" and l ook '
1760 'for the value under "Identifying your project\n\n')
1761 if not webbrowser.open(GOOG_API_CONSOLE_URI, new=1, autoraise=True):
1762 sys.stdout.write(
1763 'Launching browser appears to have failed; please navigate a browser '
1764 'to the following URL:\n%s\n' % GOOG_API_CONSOLE_URI)
1765 # Short delay; webbrowser.open on linux insists on printing out a message
1766 # which we don't want to run into the prompt for the auth code.
1767 time.sleep(2)
1768 else:
1769 sys.stdout.write(
1770 '\nPlease navigate your browser to %s,\nthen click "Services" on the '
1771 'left side panel and ensure you have Storage'
1772 '\nactivated, then click "Storage" on the left side panel and find '
1773 'the "x-goog-project-id" on that page.\n' %
1774 GOOG_API_CONSOLE_URI)
1775 default_project_id = raw_input('What is your project-id? ')
1776 project_id_section_prelude = """
1777 # 'default_project_id' specifies the default Google Storage project ID to use
1778 # with the 'mb' and 'ls' commands. If defined it overrides the default value
1779 # you set in the API Console. Either of these defaults can be overridden
1780 # by specifying the -p option to the 'mb' and 'ls' commands.
1781 """
1782 if default_project_id:
1783 config_file.write('%sdefault_project_id = %s\n\n\n' %
1784 (project_id_section_prelude, default_project_id))
1785 else:
1786 sys.stderr.write('No default project ID entered. You will need to edit '
1787 'the default_project_id value\nin your boto config file '
1788 'before using "gsutil ls gs://" or "mb" commands'
1789 'with the default API version 2.\n')
1790 config_file.write('%s#default_project_id = <value>\n\n\n' %
1791 project_id_section_prelude)
1792
1793 # Write the config file OAuth2 section.
1794 config_file.write(CONFIG_OAUTH2_CONFIG_CONTENT)
1795
1796 def CreateConfigCommand(self, args_unused=[], sub_opts=[],
1797 headers_unused=None, debug=0):
1798 """Implementation of the 'config' command.
1799
1800 Args:
1801 sub_opts: list of command-specific options from getopt.
1802 debug: debug level to pass in to boto connection (range 0..3).
1803
1804 Raises:
1805 CommandException: if errors encountered.
1806 """
1807 scopes = []
1808 use_oauth2 = True
1809 launch_browser = False
1810 output_file_name = None
1811 for opt, opt_arg in sub_opts:
1812 if opt == '-h':
1813 sys.stderr.write(CONFIG_COMMAND_HELP)
1814 sys.exit(0)
1815 if opt == '-a':
1816 use_oauth2 = False
1817 if opt == '-b':
1818 launch_browser = True
1819 if opt == '-f':
1820 scopes.append(SCOPE_FULL_CONTROL)
1821 if opt == '-w':
1822 scopes.append(SCOPE_READ_WRITE)
1823 if opt == '-r':
1824 scopes.append(SCOPE_READ_ONLY)
1825 if opt == '-s':
1826 scopes.append(opt_arg)
1827 if opt == '-o':
1828 output_file_name = opt_arg
1829
1830 if use_oauth2 and not _HAVE_OAUTH2:
1831 raise CommandException(
1832 "OAuth2 is only supported when running under Python 2.6 or later\n"
1833 "(unless additional dependencies are installed, "
1834 "see README for details);\n"
1835 "you are running Python %s.\nUse 'gsutil config -a' to create a "
1836 "config with Developer Key authentication credentials." % sys.version)
1837
1838 if len(scopes) == 0:
1839 scopes.append(SCOPE_FULL_CONTROL)
1840
1841 if output_file_name is None:
1842 # Use the default config file name, if it doesn't exist or can be moved
1843 # out of the way without clobbering an existing backup file.
1844 default_config_path = os.path.expanduser(os.path.join('~', '.boto'))
1845 if not os.path.exists(default_config_path):
1846 output_file_name = default_config_path
1847 else:
1848 default_config_path_bak = default_config_path + ".bak"
1849 if os.path.exists(default_config_path_bak):
1850 raise CommandException("Cannot back up existing config "
1851 "file '%s': backup file exists ('%s')."
1852 % (default_config_path, default_config_path_bak))
1853 else:
1854 try:
1855 sys.stderr.write(
1856 "Backing up existing config file '%s' to '%s'...\n"
1857 % (default_config_path, default_config_path_bak))
1858 os.rename(default_config_path, default_config_path_bak)
1859 except e:
1860 raise CommandException("Failed to back up existing config "
1861 "file ('%s' -> '%s'): %s."
1862 % (default_config_path, default_config_path_bak, e))
1863 output_file_name = default_config_path
1864
1865 if output_file_name == '-':
1866 output_file = sys.stdout
1867 else:
1868 output_file = OpenConfigFile(output_file_name)
1869 sys.stderr.write(
1870 'This script will create a boto config file at\n%s\ncontaining your '
1871 'credentials, based on your responses to the following questions.\n\n'
1872 % output_file_name)
1873
1874 try:
1875 self.WriteBotoConfigFile(output_file, use_oauth2=use_oauth2,
1876 launch_browser=launch_browser, oauth2_scopes=scopes)
1877 except Exception, e:
1878 # If an error occurred during config file creation, remove the invalid
1879 # config file.
1880 if output_file_name != '-':
1881 output_file.close()
1882 os.unlink(output_file_name)
1883 raise
1884
1885 if output_file_name != '-':
1886 output_file.close()
1887 sys.stderr.write(
1888 '\nBoto config file "%s" created. If you need to use\na proxy to '
1889 'access the Internet please see the instructions in that file.\n'
1890 % output_file_name)
OLDNEW
« 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