My favorites | Sign in
Project Home Downloads Wiki Issues Source
Checkout   Browse   Changes    
 
Changes to /trunk/src/gsutil
r12 vs. r13 Compare: vs.  Format:
Revision r13
Go to: 
Sign in to write a code review
/trunk/src/gsutil   r12 /trunk/src/gsutil   r13
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # 2 #
3 # Copyright 2010 Google Inc. 3 # Copyright 2010 Google Inc.
4 # 4 #
5 # Licensed under the Apache License, Version 2.0 (the "License"); 5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with 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 7 # You may obtain a copy of the License at
8 # 8 #
9 # http://www.apache.org/licenses/LICENSE-2.0 9 # http://www.apache.org/licenses/LICENSE-2.0
10 # 10 #
11 # Unless required by applicable law or agreed to in writing, software 11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS, 12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and 14 # See the License for the specific language governing permissions and
15 # limitations under the License. 15 # limitations under the License.
16 16
17 """Google Storage command line tool.""" 17 """Google Storage command line tool."""
18 18
19 import datetime 19 import datetime
20 import fnmatch 20 import fnmatch
21 import getopt 21 import getopt
22 import glob 22 import glob
23 import mimetypes
23 import os 24 import os
24 import platform 25 import platform
25 import re 26 import re
26 import shutil 27 import shutil
27 import signal 28 import signal
28 import stat 29 import stat
29 import sys 30 import sys
30 import tarfile 31 import tarfile
31 import tempfile 32 import tempfile
32 import xml.dom.minidom 33 import xml.dom.minidom
33 34
34 35
35 def OutputAndExit(message): 36 def OutputAndExit(message):
36 sys.stderr.write('%s\n' % message) 37 sys.stderr.write('%s\n' % message)
37 sys.exit(1) 38 sys.exit(1)
38 39
39 40
40 # Before importing boto, find where gsutil is installed and include its 41 # Before importing boto, find where gsutil is installed and include its
41 # boto sub-directory at the start of the PYTHONPATH, to ensure the versions of 42 # boto sub-directory at the start of the PYTHONPATH, to ensure the versions of
42 # gsutil and boto stay in sync after software updates. This also allows gsutil 43 # gsutil and boto stay in sync after software updates. This also allows gsutil
43 # to be used without explicitly adding it to the PYTHONPATH. 44 # to be used without explicitly adding it to the PYTHONPATH.
44 # We use realpath() below to unwind symlinks if any were used in the gsutil 45 # We use realpath() below to unwind symlinks if any were used in the gsutil
45 # installation. 46 # installation.
46 gsutil_bin_dir = os.path.realpath(os.path.dirname(sys.argv[0])) 47 gsutil_bin_dir = os.path.realpath(os.path.dirname(sys.argv[0]))
47 if not gsutil_bin_dir: 48 if not gsutil_bin_dir:
48 OutputAndExit('Unable to determine where gsutil is installed. Sorry, ' 49 OutputAndExit('Unable to determine where gsutil is installed. Sorry, '
49 'cannot run correctly without this.\n') 50 'cannot run correctly without this.\n')
50 boto_lib_dir = gsutil_bin_dir + os.sep + 'boto' 51 boto_lib_dir = gsutil_bin_dir + os.sep + 'boto'
51 if not os.path.isdir(boto_lib_dir): 52 if not os.path.isdir(boto_lib_dir):
52 OutputAndExit('There is no boto library under the gsutil install directory ' 53 OutputAndExit('There is no boto library under the gsutil install directory '
53 '(%s).\nThe gsutil command cannot work properly when installed ' 54 '(%s).\nThe gsutil command cannot work properly when installed '
54 'this way.\nPlease re-install gsutil per the installation ' 55 'this way.\nPlease re-install gsutil per the installation '
55 'instructions.' % gsutil_bin_dir) 56 'instructions.' % gsutil_bin_dir)
56 if 'PYTHONPATH' not in os.environ: 57 if 'PYTHONPATH' not in os.environ:
57 os.environ['PYTHONPATH'] = boto_lib_dir 58 os.environ['PYTHONPATH'] = boto_lib_dir
58 else: 59 else:
59 os.environ['PYTHONPATH'] = boto_lib_dir + ':' + os.getenv('PYTHONPATH') 60 os.environ['PYTHONPATH'] = boto_lib_dir + ':' + os.getenv('PYTHONPATH')
60 61
61 import boto 62 import boto
63 boto.UserAgent = boto.UserAgent + '/gsutil'
62 from boto import handler 64 from boto import handler
63 from boto.exception import BotoClientError 65 from boto.exception import BotoClientError
66 from boto.exception import InvalidAclError
64 from boto.exception import InvalidUriError 67 from boto.exception import InvalidUriError
65 from boto.exception import S3ResponseError 68 from boto.exception import S3ResponseError
66 from boto.s3.acl import CannedACLStrings
67 from boto.s3.acl import Policy
68 69
69 usage_string = """ 70 usage_string = """
70 SYNOPSIS 71 SYNOPSIS
71 gsutil [-d] command args 72 gsutil [-d] [-h header]... command args
72 73
73 -d option will show HTTP protocol detail. 74 -d option shows HTTP protocol detail.
75
76 -h option allows you to specify additional HTTP headers, for example:
77 gsutil -h "Cache-Control:public,max-age=3600" -h "Content-Type:gzip" cp * gs://bucket
74 78
75 Commands: 79 Commands:
76 Concatenate object content to stdout: 80 Concatenate object content to stdout:
77 cat [-h] uri1 [uri2...] 81 cat [-h] uri...
82 -h Prints short header for each object.
78 Copy objects: 83 Copy objects:
79 cp src_uri dst_uri 84 cp [-a canned_acl] [-t] src_uri dst_uri
80 - or - 85 - or -
81 cp uri1 [uri2 ...] dst_uri 86 cp [-a canned_acl] [-t] uri... dst_uri
87 -a Sets named canned_acl when uploaded objects created (list below).
88 -t Sets MIME type based on file extension.
89 Get ACL XML for a bucket or object (save and edit for "setacl" command):
90 getacl uri
82 List buckets or objects: 91 List buckets or objects:
83 ls [-l] uri1 [uri2...] 92 ls [-l] uri...
93 -l Prints long listing.
84 Make buckets: 94 Make buckets:
85 mb uri1 [uri2...] 95 mb uri...
86 Move/rename objects: 96 Move/rename objects:
87 mv src_uri dst_uri 97 mv src_uri dst_uri
88 - or - 98 - or -
89 mv uri1 [uri2 ...] dst_uri 99 mv uri... dst_uri
90 Remove buckets: 100 Remove buckets:
91 rb uri1 [uri2...] 101 rb uri...
92 Remove objects: 102 Remove objects:
93 rm uri1 [uri2...] 103 rm uri...
94 Set ACL on buckets and/or objects: 104 Set ACL on buckets and/or objects:
95 setacl <canned_acl_name> uri1 [uri2...] 105 setacl file-or-canned_acl_name uri...
96 canned_acl_name can be one of: "public-read", "public-read-write",
97 "authenticated-read", "bucket-owner-read", "bucket-owner-full-control"
98 106
99 Omitting URI scheme defaults to "file". For example, "dir/file.txt" is 107 Omitting URI scheme defaults to "file". For example, "dir/file.txt" is
100 equivalent to "file://dir/file.txt" 108 equivalent to "file://dir/file.txt"
101 109
102 URIs support object name wildcards, for example: 110 URIs support object name wildcards, for example:
103 gsutil cp gs://mybucket/[a-f]*.doc localdir 111 gsutil cp gs://mybucket/[a-f]*.doc localdir
104 112
105 Source directory or bucket names are implicitly wildcarded, so 113 Source directory or bucket names are implicitly wildcarded, so
106 gsutil cp localdir gs://mybucket 114 gsutil cp localdir gs://mybucket
107 will recursively copy localdir. 115 will recursively copy localdir.
116
117 canned_acl_name can be one of: "public-read", "public-read-write",
118 "authenticated-read", "bucket-owner-read", "bucket-owner-full-control"
108 """ 119 """
109 120
110 121
111 def OutputUsageAndExit(): 122 def OutputUsageAndExit():
112 sys.stderr.write(usage_string) 123 sys.stderr.write(usage_string)
113 sys.exit(0) 124 sys.exit(0)
114 125
115 126
116 def StorageUri(uri_str, disallow_object_name=False, debug=False): 127 def StorageUri(uri_str, disallow_object_name=False, debug=False):
117 """Instantiate boto.StorageUri with given debug flag with validity checks. 128 """Instantiate boto.StorageUri with given debug flag with validity checks.
118 129
119 Args: 130 Args:
120 uri_str: URI naming bucket + optional object. 131 uri_str: StorageUri naming bucket + optional object.
121 disallow_object_name: Set to true to only allow object name-less buckets. 132 disallow_object_name: Set to true to only allow object name-less buckets.
122 debug: Whether to enable debugging on StorageUri method calls. 133 debug: Whether to enable debugging on StorageUri method calls.
123 134
124 Returns: 135 Returns:
125 boto.StorageUri for given uri_str. 136 boto.StorageUri for given uri_str.
126 137
127 Raises: 138 Raises:
128 InvalidUriError: if uri_str not valid. 139 InvalidUriError: if uri_str not valid.
129 """ 140 """
130 141
131 uri = boto.storage_uri(uri_str, 'file', debug) 142 uri = boto.storage_uri(uri_str, 'file', debug)
132 if disallow_object_name and uri.object_name: 143 if disallow_object_name and uri.object_name:
133 raise InvalidUriError('Command requires a URI with no object name') 144 raise InvalidUriError('Command requires a URI with no object name')
134 if uri.bucket_name and re.search('[*?\[\]]', uri.bucket_name): 145 if uri.bucket_name and re.search('[*?\[\]]', uri.bucket_name):
135 raise InvalidUriError('Bucket names cannot contain wildcards') 146 raise InvalidUriError('Bucket names cannot contain wildcards')
136 return uri 147 return uri
137 148
138 149
139 def ExpandStorageUriGlob(bucket_obj_glob, debug): 150 def ExpandStorageUriGlob(bucket_obj_glob, headers=None, debug=False):
140 """Expands globbing (if any) in bucket_obj_glob. 151 """Expands globbing (if any) in bucket_obj_glob.
141 152
142 Args: 153 Args:
143 bucket_obj_glob: bucket+object name, possibly including glob chars. 154 bucket_obj_glob: bucket+object name, possibly including glob chars.
155 headers: dictionary containing optional HTTP headers to pass to boto.
144 debug: flag indicating whether to include debug output 156 debug: flag indicating whether to include debug output
145 157
146 Returns: 158 Returns:
147 list of uri strings, after expanding any globbing. 159 list of boto.StorageUri, after expanding any globbing.
148 160
149 For example, ExpandStorageUriGlob('gs://mybucket/*.txt', debug) might return 161 For example, ExpandStorageUriGlob('gs://mybucket/*.txt', headers, debug)
150 ['gs://mybucket/obj1.txt', 'gs://mybucket/obj2.txt'] 162 might return [StorageUri('gs://mybucket/obj1.txt'),
163 StorageUri('gs://mybucket/obj2.txt')]
151 """ 164 """
152 165
153 uri = StorageUri(bucket_obj_glob, False, debug) 166 uri = StorageUri(bucket_obj_glob, False, debug)
154 key_glob = uri.object_name 167 key_glob = uri.object_name
155 result = [] 168 result = []
156 # Avoid server round trip if input contains no glob chars. 169 # Avoid server round trip if input contains no glob chars.
157 if not re.search('[*?\[\]]', key_glob): 170 if not re.search('[*?\[\]]', key_glob):
158 result.append(bucket_obj_glob) 171 result.append(uri)
159 elif uri.is_file_uri(): 172 elif uri.is_file_uri():
160 # FileStorageUri objects don't provide a way to return a list of all 173 # FileStorageUri objects don't provide a way to return a list of all
161 # files in the 'bucket', so do our own wildcard expansion for that case. 174 # files in the 'bucket', so do our own wildcard expansion for that case.
162 filenames = glob.glob(uri.object_name) 175 filenames = glob.glob(uri.object_name)
163 for filename in filenames: 176 for filename in filenames:
164 expanded_uri = uri.clone_replace_name(filename) 177 expanded_uri = uri.clone_replace_name(filename)
165 result.append(expanded_uri.uri) 178 result.append(expanded_uri)
166 else: 179 else:
167 # BucketStorageUri with wildcarding. 180 # BucketStorageUri with wildcarding.
168 objs = uri.get_bucket() 181 objs = uri.get_bucket(False, headers)
169 for obj in objs: 182 for obj in objs:
170 if fnmatch.fnmatch(obj.name, key_glob): 183 if fnmatch.fnmatch(obj.name, key_glob):
171 # Replace wildcard name in URI with specific matched obj.name 184 # Replace wildcard name in URI with specific matched obj.name
172 expanded_uri = uri.clone_replace_name(obj.name) 185 expanded_uri = uri.clone_replace_name(obj.name)
173 result.append(expanded_uri.__str__()) 186 result.append(expanded_uri)
174 return result 187 return result
175 188
176 189
177 def ExpandWildcardsAndContainers(uri_strs, debug): 190 def ExpandWildcardsAndContainers(uri_strs, headers=None, debug=False):
178 """Expands any URI globbing, object-less bucket names, or directory names. 191 """Expands any URI globbing, object-less bucket names, or directory names.
179 192
180 Args: 193 Args:
181 uri_strs: URI strings needing expansion 194 uri_strs: URI strings needing expansion
195 headers: dictionary containing optional HTTP headers to pass to boto.
182 debug: flag indicating whether to include debug output 196 debug: flag indicating whether to include debug output
183 197
184 Returns: 198 Returns:
185 list of uri strings, after expanding any globbing and recursively 199 list of boto.StorageUri, after expanding any globbing and recursively
186 walking directories. 200 walking directories.
187 """ 201 """
188 202
189 exp_uri_strs = [] 203 result = []
190 for uri_str in uri_strs: 204 for uri_str in uri_strs:
191 for exp_uri_str in ExpandStorageUriGlob(uri_str, debug): 205 for exp_uri in ExpandStorageUriGlob(uri_str, headers, debug):
192 exp_uri = StorageUri(exp_uri_str, False, debug)
193 if exp_uri.is_file_uri() and exp_uri.names_container(): 206 if exp_uri.is_file_uri() and exp_uri.names_container():
194 # exp_uri_str is a file:// URI that names a directory, so include 207 # exp_uri is a file:// URI that names a directory, so include
195 # all its nested files. 208 # all its nested files.
196 for root, unused_dirs, files in os.walk(exp_uri.object_name): 209 for root, unused_dirs, files in os.walk(exp_uri.object_name):
197 for name in files: 210 for name in files:
198 exp_uri_strs.append(os.path.join(root, name)) 211 result.append(StorageUri(os.path.join(root, name)))
199 elif exp_uri.is_cloud_uri() and exp_uri.names_container(): 212 elif exp_uri.is_cloud_uri() and exp_uri.names_container():
200 # exp_uri_str is an object-less bucket URI, so include 213 # exp_uri is an object-less bucket URI, so include
201 # all its nested objects. 214 # all its nested objects.
202 bucket_wildcard = exp_uri.clone_replace_name('*').uri 215 bucket_wildcard = exp_uri.clone_replace_name('*').uri
203 obj_list = ExpandStorageUriGlob(bucket_wildcard, debug) 216 uris = ExpandStorageUriGlob(bucket_wildcard, headers, debug)
204 for exp_bucket_uri_str in obj_list: 217 for uri in uris:
205 exp_uri_strs.append(exp_bucket_uri_str) 218 result.append(uri)
206 else: 219 else:
207 exp_uri_strs.append(exp_uri_str) 220 result.append(exp_uri)
208 return exp_uri_strs 221 return result
209 222
210 223
211 def InsistUriNamesContainer(command, uri): 224 def InsistUriNamesContainer(command, uri):
212 """Prints error and exists if URI doesn't name a directory or bucket. 225 """Prints error and exists if URI doesn't name a directory or bucket.
213 226
214 Args: 227 Args:
215 command: command being run 228 command: command being run
216 uri: StorageUri to check 229 uri: StorageUri to check
217 """ 230 """
218 231
219 if uri.names_singleton(): 232 if uri.names_singleton():
220 OutputAndExit('Destination URI must name a bucket or directory ' 233 OutputAndExit('destination StorageUri must name a bucket or directory '
221 'for the multiple source\nform of the "%s" command.' % 234 'for the multiple source\nform of the "%s" command.' %
222 command) 235 command)
223 236
224 def CatCommand(args, sub_opts, debug): 237
238 def CatCommand(args, sub_opts, headers=None, debug=False):
225 """Implementation of cat command. 239 """Implementation of cat command.
226 240
227 Args: 241 Args:
228 args: command-line arguments 242 args: command-line arguments
229 sub_opts: command-specific options from getopt. 243 sub_opts: command-specific options from getopt.
244 headers: dictionary containing optional HTTP headers to pass to boto.
230 debug: flag indicating whether to include debug output 245 debug: flag indicating whether to include debug output
231 """ 246 """
232 247
233 show_header = False 248 show_header = False
234 for o, unused_a in sub_opts: 249 for o, unused_a in sub_opts:
235 if o == '-h': 250 if o == '-h':
236 show_header = True 251 show_header = True
237 252
238 # Expand object name globs, if any. 253 # Expand object name globs, if any.
239 exp_args = [] 254 exp_uris = []
240 for uri_str in args: 255 for uri_str in args:
241 for exp_uri_str in ExpandStorageUriGlob(uri_str, debug): 256 for exp_uri in ExpandStorageUriGlob(uri_str, headers, debug):
242 exp_args.append(exp_uri_str) 257 exp_uris.append(exp_uri)
243 if not exp_args: 258 if not exp_uris:
244 OutputAndExit('No objects matched.') 259 OutputAndExit('No objects matched.')
245 printed_one = False 260 printed_one = False
246 for uri_str in exp_args: 261 for uri in exp_uris:
247 uri = StorageUri(uri_str, False, debug)
248 if not uri.object_name: 262 if not uri.object_name:
249 OutputAndExit('"cat" command must specify objects.') 263 OutputAndExit('"cat" command must specify objects.')
250 if show_header: 264 if show_header:
251 if printed_one: 265 if printed_one:
252 print 266 print
253 print('==> %s <==' % uri_str) 267 print '==> %s <==' % uri.__str__()
254 printed_one = True 268 printed_one = True
255 sys.stdout.write(uri.get_contents_as_string()) 269 sys.stdout.write(uri.get_contents_as_string(False, headers))
256 270
257 def SetAclCommand(args, unused_sub_opts, debug): 271
272 def SetAclCommand(args, unused_sub_opts, headers=None, debug=False):
258 """Implementation of setacl command. 273 """Implementation of setacl command.
259 274
260 Args: 275 Args:
261 args: command-line arguments 276 args: command-line arguments
262 unused_sub_opts: command-specific options from getopt. 277 unused_sub_opts: command-specific options from getopt.
278 headers: dictionary containing optional HTTP headers to pass to boto.
263 debug: flag indicating whether to include debug output 279 debug: flag indicating whether to include debug output
264 """ 280 """
265 281
266 acl_arg = args[0] 282 acl_arg = args[0]
267 # Expand object name globs, if any. 283 # Expand object name globs, if any.
268 exp_args = [] 284 exp_uris = []
269 for uri_str in args[1:]: 285 for uri_str in args[1:]:
270 for exp_uri_str in ExpandStorageUriGlob(uri_str, debug): 286 for exp_uri in ExpandStorageUriGlob(uri_str, headers, debug):
271 exp_args.append(exp_uri_str) 287 exp_uris.append(exp_uri)
272 288
273 # Determine whether acl_arg names a file vs. a canned ACL name. 289 # Disallow setacl command spanning multiple providers because
274 # Note: passing an XML file is currently experimental. 290 # there are differences in the ACL models.
291 provider = None
292 for uri in exp_uris:
293 if not provider:
294 provider = uri.provider
295 elif uri.provider != provider:
296 OutputAndExit('"setacl" command spanning multiple providers not allowed.')
297
298 # Get ACL object from connection for the first URI, for interpreting the
299 # ACL. This won't fail because the main startup code insists on 1 arg
300 # for this command.
301 storage_uri = exp_uris[0]
302 acl_class = storage_uri.acl_class()
303 canned_acls = storage_uri.canned_acls()
304
305 # Determine whether acl_arg names a file containing XML ACL text vs. the
306 # string name of a canned ACL.
275 if os.path.isfile(acl_arg): 307 if os.path.isfile(acl_arg):
276 acl_file = open(acl_arg, 'r') 308 acl_file = open(acl_arg, 'r')
277 acl_txt = acl_file.read() 309 acl_txt = acl_file.read()
278 acl_file.close() 310 acl_file.close()
279 # We need a bucket object for parsing the XML into a Policy, so just 311 acl_obj = acl_class()
280 # use the bucket object from the first URI. This won't fail because the 312 h = handler.XmlHandler(acl_obj, storage_uri.get_bucket())
281 # main startup code insists on 1 arg for this command.
282 bucket = StorageUri(exp_args[0], False, debug).get_bucket()
283 policy = Policy(bucket)
284 h = handler.XmlHandler(policy, bucket)
285 xml.sax.parseString(acl_txt, h) 313 xml.sax.parseString(acl_txt, h)
286 acl_arg = policy 314 acl_arg = acl_obj
287 else: 315 else:
288 # No file exists, so expect a canned ACL string. 316 # No file exists, so expect a canned ACL string.
289 if acl_arg not in CannedACLStrings: 317 if acl_arg not in canned_acls:
290 OutputAndExit('Invalid canned ACL "%s".' % acl_arg) 318 OutputAndExit('Invalid canned ACL "%s".' % acl_arg)
291 319
292 # Now iterate over URI args and set the ACL on each. 320 # Now iterate over URIs and set the ACL on each.
293 for uri_str in exp_args: 321 for uri in exp_uris:
294 uri = StorageUri(uri_str, False, debug) 322 if len(exp_uris) > 1:
295 if len(exp_args) > 1:
296 # Progress indicator 323 # Progress indicator
297 print 'Setting ACL on %s...' % uri 324 print 'Setting ACL on %s...' % uri
298 uri.set_acl(acl_arg, uri.object_name) 325 uri.set_acl(acl_arg, uri.object_name, False, headers)
299 326
300 327
301 def ExplainIfSudoNeeded(tf, dirs_to_remove): 328 def ExplainIfSudoNeeded(tf, dirs_to_remove):
302 """Explains what to do if sudo needed to update gsutil software. 329 """Explains what to do if sudo needed to update gsutil software.
303 330
304 Happens if gsutil was previously installed by a different user (typically if 331 Happens if gsutil was previously installed by a different user (typically if
305 someone originally installed in a shared file system location, using sudo). 332 someone originally installed in a shared file system location, using sudo).
306 333
307 Args: 334 Args:
308 tf: opened TarFile. 335 tf: opened TarFile.
309 dirs_to_remove: list of directories to remove. 336 dirs_to_remove: list of directories to remove.
310 """ 337 """
311 338
312 system = platform.system() 339 system = platform.system()
313 # If running under Windows we don't need (or have) sudo. 340 # If running under Windows we don't need (or have) sudo.
314 if system.lower().startswith('windows'): 341 if system.lower().startswith('windows'):
315 return 342 return
316 343
317 user_id = os.getuid() 344 user_id = os.getuid()
318 if (os.stat(gsutil_bin_dir).st_uid == user_id and 345 if (os.stat(gsutil_bin_dir).st_uid == user_id and
319 os.stat(boto_lib_dir).st_uid == user_id): 346 os.stat(boto_lib_dir).st_uid == user_id):
320 return 347 return
321 348
322 # Won't fail - this command runs after main startup code that insists on 349 # Won't fail - this command runs after main startup code that insists on
323 # having a config file. 350 # having a config file.
324 config_file = GetBotoConfigFileList()[0] 351 config_file = GetBotoConfigFileList()[0]
325 CleanUpUpdateCommand(tf, dirs_to_remove) 352 CleanUpUpdateCommand(tf, dirs_to_remove)
326 OutputAndExit(('Since it was installed by a different user previously, ' 353 OutputAndExit(('Since it was installed by a different user previously, '
327 'you will need to update using the following commands.\n' 354 'you will need to update using the following commands.\n'
328 'You will be prompted for your password, and the install ' 355 'You will be prompted for your password, and the install '
329 'will run as "root". If you\'re unsure what this means please ' 356 'will run as "root". If you\'re unsure what this means please '
330 'ask your system administrator for help:\n' 357 'ask your system administrator for help:\n'
331 '\tchmod 644 %s\n' 358 '\tchmod 644 %s\n'
332 '\tsudo env BOTO_CONFIG=%s gsutil update\n' 359 '\tsudo env BOTO_CONFIG=%s gsutil update\n'
333 '\tchmod 600 %s') % (config_file, config_file, config_file)) 360 '\tchmod 600 %s') % (config_file, config_file, config_file))
334 361
335 362
336 def CleanUpUpdateCommand(tf, dirs_to_remove): 363 def CleanUpUpdateCommand(tf, dirs_to_remove):
337 """Cleans up temp files etc. from running update command. 364 """Cleans up temp files etc. from running update command.
338 365
339 Args: 366 Args:
340 tf: opened TarFile. 367 tf: opened TarFile.
341 dirs_to_remove: list of directories to remove. 368 dirs_to_remove: list of directories to remove.
342 369
343 """ 370 """
344 tf.close() 371 tf.close()
345 for directory in dirs_to_remove: 372 for directory in dirs_to_remove:
346 shutil.rmtree(directory) 373 shutil.rmtree(directory)
347 374
348 375
349 def LoadVersionString(): 376 def LoadVersionString():
350 """Loads version string for currently installed gsutil command. 377 """Loads version string for currently installed gsutil command.
351 378
352 Returns: 379 Returns:
353 Version string. 380 Version string.
354 """ 381 """
355 382
356 ver_file_path = gsutil_bin_dir + os.sep + 'VERSION' 383 ver_file_path = gsutil_bin_dir + os.sep + 'VERSION'
357 if not os.path.isfile(ver_file_path): 384 if not os.path.isfile(ver_file_path):
358 OutputAndExit('%s not found. Did you install the\ncomplete gsutil software ' 385 OutputAndExit('%s not found. Did you install the\ncomplete gsutil software '
359 'after the gsutil "update" command was implemented?' % 386 'after the gsutil "update" command was implemented?' %
360 ver_file_path) 387 ver_file_path)
361 ver_file = open(ver_file_path, 'r') 388 ver_file = open(ver_file_path, 'r')
362 installed_version_string = ver_file.read().rstrip('\n') 389 installed_version_string = ver_file.read().rstrip('\n')
363 ver_file.close() 390 ver_file.close()
364 return installed_version_string 391 return installed_version_string
365 392
366 393
367 def UpdateCommand(unused_args, sub_opts, debug): 394 def UpdateCommand(unused_args, sub_opts, headers=None, debug=False):
368 """Implementation of experimental update command. 395 """Implementation of experimental update command.
369 396
370 Args: 397 Args:
371 unused_args: command-line arguments 398 unused_args: command-line arguments
372 sub_opts: command-specific options from getopt. 399 sub_opts: command-specific options from getopt.
400 headers: dictionary containing optional HTTP headers to pass to boto.
373 debug: flag indicating whether to include debug output 401 debug: flag indicating whether to include debug output
374 """ 402 """
375 403
376 installed_version_string = LoadVersionString() 404 installed_version_string = LoadVersionString()
377 405
378 dirs_to_remove = [] 406 dirs_to_remove = []
379 # Retrieve gsutil tarball and check if it's newer than installed code. 407 # Retrieve gsutil tarball and check if it's newer than installed code.
380 # TODO: Expose a way to set metadata on objects via gsutil, then store 408 # TODO: Store this version info as metadata on the tarball object and
381 # this version info there and change this command's implementation to check 409 # change this command's implementation to check that metadata instead of
382 # that metadata instead of downloading the tarball to check the version info. 410 # downloading the tarball to check the version info.
383 tmp_dir = tempfile.mkdtemp() 411 tmp_dir = tempfile.mkdtemp()
384 dirs_to_remove.append(tmp_dir) 412 dirs_to_remove.append(tmp_dir)
385 os.chdir(tmp_dir) 413 os.chdir(tmp_dir)
386 print 'Checking for software update...' 414 print 'Checking for software update...'
387 CopyObjsCommand(['gs://pub/gsutil.tar.gz', 'file://gsutil.tar.gz'], [], debug) 415 CopyObjsCommand(['gs://pub/gsutil.tar.gz', 'file://gsutil.tar.gz'], [],
416 headers, debug)
388 tf = tarfile.open('gsutil.tar.gz') 417 tf = tarfile.open('gsutil.tar.gz')
389 tf.errorlevel = 1 # So fatal tarball unpack errors raise exceptions. 418 tf.errorlevel = 1 # So fatal tarball unpack errors raise exceptions.
390 tf.extract('./gsutil/VERSION') 419 tf.extract('./gsutil/VERSION')
391 ver_file = open('gsutil/VERSION', 'r') 420 ver_file = open('gsutil/VERSION', 'r')
392 latest_version_string = ver_file.read().rstrip('\n') 421 latest_version_string = ver_file.read().rstrip('\n')
393 ver_file.close() 422 ver_file.close()
394 423
395 # The force_update option works around a problem with the way the 424 # The force_update option works around a problem with the way the
396 # first gsutil "update" command exploded the gsutil and boto directories, 425 # first gsutil "update" command exploded the gsutil and boto directories,
397 # which didn't correctly install boto. People running that older code can 426 # which didn't correctly install boto. People running that older code can
398 # run "gsutil update" (to update to the newer gsutil update code) followed by 427 # run "gsutil update" (to update to the newer gsutil update code) followed by
399 # "gsutil update -f" (which will then update the boto code, even though the 428 # "gsutil update -f" (which will then update the boto code, even though the
400 # VERSION is already the latest version). 429 # VERSION is already the latest version).
401 force_update = False 430 force_update = False
402 for o, unused_a in sub_opts: 431 for o, unused_a in sub_opts:
403 if o == '-f': 432 if o == '-f':
404 force_update = True 433 force_update = True
405 if not force_update and installed_version_string == latest_version_string: 434 if not force_update and installed_version_string == latest_version_string:
406 CleanUpUpdateCommand(tf, dirs_to_remove) 435 CleanUpUpdateCommand(tf, dirs_to_remove)
407 OutputAndExit('You have the latest version of gsutil installed.') 436 OutputAndExit('You have the latest version of gsutil installed.')
408 437
409 print(('This command will update to the "%s" version of\ngsutil at %s') % 438 print(('This command will update to the "%s" version of\ngsutil at %s') %
410 (latest_version_string, gsutil_bin_dir)) 439 (latest_version_string, gsutil_bin_dir))
411 ExplainIfSudoNeeded(tf, tmp_dir) 440 ExplainIfSudoNeeded(tf, tmp_dir)
412 441
413 answer = raw_input('Proceed (Note: experimental command)? [y/N] ') 442 answer = raw_input('Proceed (Note: experimental command)? [y/N] ')
414 if not answer or answer.lower()[0] != 'y': 443 if not answer or answer.lower()[0] != 'y':
415 CleanUpUpdateCommand(tf, dirs_to_remove) 444 CleanUpUpdateCommand(tf, dirs_to_remove)
416 OutputAndExit('Not running update.') 445 OutputAndExit('Not running update.')
417 446
418 # Ignore keyboard interrupts during the update to reduce the chance someone 447 # Ignore keyboard interrupts during the update to reduce the chance someone
419 # hitting ^C leaves gsutil in a broken state. 448 # hitting ^C leaves gsutil in a broken state.
420 signal.signal(signal.SIGINT, signal.SIG_IGN) 449 signal.signal(signal.SIGINT, signal.SIG_IGN)
421 450
422 # gsutil_bin_dir lists the path where the code should end up (like 451 # gsutil_bin_dir lists the path where the code should end up (like
423 # /usr/local/gsutil), which is one level down from the relative path in the 452 # /usr/local/gsutil), which is one level down from the relative path in the
424 # tarball (since the latter creates files in ./gsutil). So, we need to 453 # tarball (since the latter creates files in ./gsutil). So, we need to
425 # extract at the parent directory level. 454 # extract at the parent directory level.
426 gsutil_bin_parent_dir = os.path.dirname(gsutil_bin_dir) 455 gsutil_bin_parent_dir = os.path.dirname(gsutil_bin_dir)
427 456
428 # Extract tarball to a temporary directory in a sibling to gsutil_bin_dir. 457 # Extract tarball to a temporary directory in a sibling to gsutil_bin_dir.
429 old_dir = tempfile.mkdtemp(dir=gsutil_bin_parent_dir) 458 old_dir = tempfile.mkdtemp(dir=gsutil_bin_parent_dir)
430 new_dir = tempfile.mkdtemp(dir=gsutil_bin_parent_dir) 459 new_dir = tempfile.mkdtemp(dir=gsutil_bin_parent_dir)
431 dirs_to_remove.append(old_dir) 460 dirs_to_remove.append(old_dir)
432 dirs_to_remove.append(new_dir) 461 dirs_to_remove.append(new_dir)
433 try: 462 try:
434 tf.extractall(path=new_dir) 463 tf.extractall(path=new_dir)
435 except Exception, e: 464 except Exception, e:
436 CleanUpUpdateCommand(tf, dirs_to_remove) 465 CleanUpUpdateCommand(tf, dirs_to_remove)
437 OutputAndExit('Update failed: %s.' % e) 466 OutputAndExit('Update failed: %s.' % e)
438 467
439 # Move old installation aside and new into place. 468 # Move old installation aside and new into place.
440 os.rename(gsutil_bin_dir, old_dir + os.sep + 'old') 469 os.rename(gsutil_bin_dir, old_dir + os.sep + 'old')
441 os.rename(new_dir + os.sep + 'gsutil', gsutil_bin_dir) 470 os.rename(new_dir + os.sep + 'gsutil', gsutil_bin_dir)
442 CleanUpUpdateCommand(tf, dirs_to_remove) 471 CleanUpUpdateCommand(tf, dirs_to_remove)
443 signal.signal(signal.SIGINT, signal.SIG_DFL) 472 signal.signal(signal.SIGINT, signal.SIG_DFL)
444 print 'Update complete.' 473 print 'Update complete.'
445 474
446 475
447 def CheckForDirFileConflict(src_uri, dst_path): 476 def CheckForDirFileConflict(src_uri, dst_path):
448 """Checks whether copying src_uri into dst_path is not possible. 477 """Checks whether copying src_uri into dst_path is not possible.
449 478
450 This happens if a directory exists in local file system where a file needs 479 This happens if a directory exists in local file system where a file needs
451 to go or vice versa. In that case we print an error message and exits. 480 to go or vice versa. In that case we print an error message and exits.
452 Example: if the file "./x" exists and you try to do: 481 Example: if the file "./x" exists and you try to do:
453 gsutil cp gs://mybucket/x/y . 482 gsutil cp gs://mybucket/x/y .
454 the request can't succeed because it requires a directory where 483 the request can't succeed because it requires a directory where
455 the file x exists. 484 the file x exists.
456 485
457 Args: 486 Args:
458 src_uri: source URI of copy 487 src_uri: source StorageUri of copy
459 dst_path: destination path. 488 dst_path: destination path.
460 """ 489 """
461 490
462 final_dir = os.path.dirname(dst_path) 491 final_dir = os.path.dirname(dst_path)
463 if os.path.isfile(final_dir): 492 if os.path.isfile(final_dir):
464 OutputAndExit('Cannot retrieve %s because it a file exists where a ' 493 OutputAndExit('Cannot retrieve %s because it a file exists where a '
465 'directory needs to be created (%s).' % (src_uri, final_dir)) 494 'directory needs to be created (%s).' % (src_uri, final_dir))
466 if os.path.isdir(dst_path): 495 if os.path.isdir(dst_path):
467 OutputAndExit('Cannot retrieve %s because a directory exists ' 496 OutputAndExit('Cannot retrieve %s because a directory exists '
468 '(%s) where the file needs to be created.' % 497 '(%s) where the file needs to be created.' %
469 (src_uri, dst_path)) 498 (src_uri, dst_path))
470 499
471 500
472 def ReportNoMatchesAndExit(uri): 501 def ReportNoMatchesAndExit(uri):
473 """Reports no URI wildcard matches and exits. 502 """Reports no URI wildcard matches and exits.
474 503
475 Args: 504 Args:
476 uri: the URI that didn't match. 505 uri: the StorageUri that didn't match.
477 """ 506 """
478 if uri.is_file_uri(): 507 if uri.is_file_uri():
479 OutputAndExit('"%s" matches no files.' % uri.uri) 508 OutputAndExit('"%s" matches no files.' % uri.uri)
480 else: 509 else:
481 OutputAndExit('"%s" matches no objects.' % uri.uri) 510 OutputAndExit('"%s" matches no objects.' % uri.uri)
482 511
483 512
484 def GetAclCommand(args, unused_sub_opts, debug): 513 def GetAclCommand(args, unused_sub_opts, headers=None, debug=False):
485 """Implementation of experimental getacl command. 514 """Implementation of getacl command.
486 515
487 Args: 516 Args:
488 args: command-line arguments 517 args: command-line arguments
489 unused_sub_opts: command-specific options from getopt. 518 unused_sub_opts: command-specific options from getopt.
519 headers: dictionary containing optional HTTP headers to pass to boto.
490 debug: flag indicating whether to include debug output 520 debug: flag indicating whether to include debug output
491 """ 521 """
492 522
493 # Wildcarding is allowed but must resolve to just one object. 523 # Wildcarding is allowed but must resolve to just one object.
494 uris = ExpandStorageUriGlob(args[0], debug) 524 uris = ExpandStorageUriGlob(args[0], headers, debug)
495 if len(uris) != 1: 525 if len(uris) != 1:
496 OutputAndExit('Wildcards must resolve to exactly one object for "getacl" ' 526 OutputAndExit('Wildcards must resolve to exactly one object for "getacl" '
497 'command.') 527 'command.')
498 uri = StorageUri(uris[0], False, debug) 528 uri = uris[0]
499 if not uri.bucket_name: 529 if not uri.bucket_name:
500 OutputAndExit('"getacl" command must specify a bucket or object.') 530 OutputAndExit('"getacl" command must specify a bucket or object.')
501 acl = uri.get_acl() 531 acl = uri.get_acl(False, headers)
502 # Pretty-print the XML to make it more easily human editable. 532 # Pretty-print the XML to make it more easily human editable.
503 parsed_xml = xml.dom.minidom.parseString(acl.to_xml()) 533 parsed_xml = xml.dom.minidom.parseString(acl.to_xml())
504 print parsed_xml.toprettyxml(indent=' ') 534 print parsed_xml.toprettyxml(indent=' ')
505 535
506 def PerformCopy(src_uri, dst_uri): 536
537 def PerformCopy(src_uri, dst_uri, sub_opts, headers):
507 """Helper method for CopyObjsCommand. 538 """Helper method for CopyObjsCommand.
508 539
509 Args: 540 Args:
510 src_uri source URI for copy. 541 src_uri: source StorageUri for copy.
511 dst_uri: destination URI for copy. 542 dst_uri: destination StorageUri for copy.
543 sub_opts: command-specific options from getopt.
544 headers: dictionary containing optional HTTP headers to pass to boto.
512 """ 545 """
513 546
514 src_key = src_uri.get_key() 547 # Make a copy of the input headers each time so we can set a different
548 # MIME type for each object.
549 metadata = headers.copy()
550 canned_acl = None
551 for o, a in sub_opts:
552 if o == "-a":
553 canned_acls = dst_uri.canned_acls()
554 if a not in canned_acls:
555 OutputAndExit('Invalid canned ACL "%s".' % a)
556 canned_acl = a
557 elif o == "-t":
558 mimetype_tuple = mimetypes.guess_type(src_uri.object_name)
559 mime_type = mimetype_tuple[0]
560 content_encoding = mimetype_tuple[1]
561 if mime_type:
562 metadata['Content-Type'] = mime_type
563 print '\t[Setting Content-Type=%s]' % mime_type
564 else:
565 print '\t[Unknown content type -> using application/octet stream]'
566 if content_encoding:
567 metadata['Content-Encoding'] = content_encoding
568
569 src_key = src_uri.get_key(False, headers)
515 if not src_key: 570 if not src_key:
516 OutputAndExit('"%s" does not exist.' % src_uri) 571 OutputAndExit('"%s" does not exist.' % src_uri)
517 572
518 # Separately handle cases to avoid extra file and network copying of 573 # Separately handle cases to avoid extra file and network copying of
519 # potentially very large files/objects. 574 # potentially very large files/objects.
520 575
521 if (src_uri.is_cloud_uri() and dst_uri.is_cloud_uri() and 576 if (src_uri.is_cloud_uri() and dst_uri.is_cloud_uri() and
522 src_uri.provider == dst_uri.provider): 577 src_uri.provider == dst_uri.provider):
523 # Object -> object, within same provider (uses x-<provider>-copy-source 578 # Object -> object, within same provider (uses x-<provider>-copy-source
524 # metadata HTTP header to request copying at the server): 579 # metadata HTTP header to request copying at the server). (Note: boto
525 src_bucket = src_uri.get_bucket() 580 # does not currently provide a way to pass canned_acl when copying from
526 dst_bucket = dst_uri.get_bucket() 581 # object-to-object through x-<provider>-copy-source):
582 src_bucket = src_uri.get_bucket(False, headers)
583 dst_bucket = dst_uri.get_bucket(False, headers)
527 dst_bucket.copy_key(dst_uri.object_name, src_bucket.name, 584 dst_bucket.copy_key(dst_uri.object_name, src_bucket.name,
528 src_uri.object_name) 585 src_uri.object_name, metadata)
529 return 586 return
530 587
531 dst_key = dst_uri.new_key() 588 dst_key = dst_uri.new_key(False, headers)
532 if src_uri.is_file_uri() and dst_uri.is_cloud_uri(): 589 if src_uri.is_file_uri() and dst_uri.is_cloud_uri():
533 # File -> object: 590 # File -> object:
534 dst_key.set_contents_from_file(src_key.fp) 591 fname_parts = src_uri.object_name.split('.')
592 dst_key.set_contents_from_file(src_key.fp, metadata, policy=canned_acl)
535 elif src_uri.is_cloud_uri() and dst_uri.is_file_uri(): 593 elif src_uri.is_cloud_uri() and dst_uri.is_file_uri():
536 # Object -> file: 594 # Object -> file:
537 src_key.get_file(dst_key.fp) 595 src_key.get_file(dst_key.fp, headers)
538 elif src_uri.is_file_uri() and dst_uri.is_file_uri(): 596 elif src_uri.is_file_uri() and dst_uri.is_file_uri():
539 # File -> file: 597 # File -> file:
540 dst_key.set_contents_from_file(src_key.fp) 598 dst_key.set_contents_from_file(src_key.fp, metadata)
541 else: 599 else:
542 # We implement cross-provider object copy through a local temp file: 600 # We implement cross-provider object copy through a local temp file:
543 tmp = tempfile.TemporaryFile() 601 tmp = tempfile.TemporaryFile()
544 src_key.get_file(tmp) 602 src_key.get_file(tmp, headers)
545 tmp.seek(0) 603 tmp.seek(0)
546 dst_key.set_contents_from_file(tmp) 604 dst_key.set_contents_from_file(tmp, metadata)
547 605
548 606
549 def CopyObjsCommand(args, unused_sub_opts, debug): 607 def CopyObjsCommand(args, sub_opts, headers=None, debug=False):
550 """Implementation of cp command. 608 """Implementation of cp command.
551 609
552 Args: 610 Args:
553 args: command-line arguments 611 args: command-line arguments
554 unused_sub_opts: command-specific options from getopt. 612 sub_opts: command-specific options from getopt.
613 headers: dictionary containing optional HTTP headers to pass to boto.
555 debug: flag indicating whether to include debug output 614 debug: flag indicating whether to include debug output
556 """ 615 """
557 616
558 src_uri_strs = args[0:len(args)-1] 617 src_uri_strs = args[0:len(args)-1]
559 dst_uri = StorageUri(args[-1], False, debug) 618 dst_uri = StorageUri(args[-1], False, debug)
560 multi_obj_copy = True 619 multi_obj_copy = True
561 620
562 # Expand wildcards and containers in source URIs. 621 # Expand wildcards and containers in source StorageUris.
563 exp_src_uri_strs = ExpandWildcardsAndContainers(src_uri_strs, debug) 622 exp_src_uris = ExpandWildcardsAndContainers(src_uri_strs, headers, debug)
564 623
565 # Abort if wildcarding produced no matches. 624 # Abort if wildcarding produced no matches.
566 if not exp_src_uri_strs: 625 if not exp_src_uris:
567 ReportNoMatches(StorageUri(src_uri_strs[0])) 626 ReportNoMatchesAndExit(StorageUri(src_uri_strs[0]))
568 627
569 # If there is 1 source arg after expansion, with src_uri naming an 628 # If there is 1 source arg after expansion, with src_uri naming an
570 # object-less bucket and dst_uri naming a directory, handle two cases to 629 # object-less bucket and dst_uri naming a directory, handle two cases to
571 # make copy command work like UNIX "cp -r" works: 630 # make copy command work like UNIX "cp -r" works:
572 # a) if no directory exists for dst_uri copy objects to a new directory 631 # a) if no directory exists for dst_uri copy objects to a new directory
573 # with the dst_uri name, e.g., "bucket/a" -> "dir/a" 632 # with the dst_uri name, e.g., "bucket/a" -> "dir/a"
574 # b) if a directory exists for dst_uri copy objects to a new directory 633 # b) if a directory exists for dst_uri copy objects to a new directory
575 # under that directory, e.g., "bucket/a" -> "dir/bucket/a" 634 # under that directory, e.g., "bucket/a" -> "dir/bucket/a"
576 if len(exp_src_uri_strs) == 1: 635 if len(exp_src_uris) == 1:
577 src_uri_to_check = StorageUri(exp_src_uri_strs[0]) 636 src_uri_to_check = exp_src_uris[0]
578 if src_uri_to_check.names_container(): 637 if src_uri_to_check.names_container():
579 if dst_uri.names_container() and os.path.exists(dst_uri.object_name): 638 if dst_uri.names_container() and os.path.exists(dst_uri.object_name):
580 dst_uri = dst_uri.clone_replace_name(dst_uri.object_name + os.sep + 639 dst_uri = dst_uri.clone_replace_name(dst_uri.object_name + os.sep +
581 src_uri_to_check.bucket_name) 640 src_uri_to_check.bucket_name)
582 else: 641 else:
583 multi_obj_copy = False 642 multi_obj_copy = False
584 643
585 if (multi_obj_copy and dst_uri.is_file_uri() 644 if (multi_obj_copy and dst_uri.is_file_uri()
586 and not os.path.exists(dst_uri.object_name)): 645 and not os.path.exists(dst_uri.object_name)):
587 os.makedirs(dst_uri.object_name) 646 os.makedirs(dst_uri.object_name)
588 647
589 if multi_obj_copy: 648 if multi_obj_copy:
590 InsistUriNamesContainer('cp', dst_uri) 649 InsistUriNamesContainer('cp', dst_uri)
591 650
592 # Abort if any source overlaps with a dest. 651 # Abort if any source overlaps with a dest.
593 for src_uri_str in exp_src_uri_strs: 652 for src_uri in exp_src_uris:
594 src_uri = StorageUri(src_uri_str, False, debug)
595 if (src_uri.equals(dst_uri) or 653 if (src_uri.equals(dst_uri) or
596 # Example case: gsutil cp gs://mybucket/a/bb mybucket 654 # Example case: gsutil cp gs://mybucket/a/bb mybucket
597 (dst_uri.is_cloud_uri() and src_uri.uri.find(dst_uri.uri) != -1)): 655 (dst_uri.is_cloud_uri() and src_uri.uri.find(dst_uri.uri) != -1)):
598 OutputAndExit('Overlapping source and dest URIs not allowed.') 656 OutputAndExit('Overlapping source and dest URIs not allowed.')
599 657
600 # Now iterate over expanded src URIs, and perform copy operations. 658 # Now iterate over expanded src URIs, and perform copy operations.
601 for src_uri_str in exp_src_uri_strs: 659 for src_uri in exp_src_uris:
602 src_uri = StorageUri(src_uri_str, False, debug) 660 if len(exp_src_uris) > 1:
603 if len(exp_src_uri_strs) > 1:
604 # Progress indicator 661 # Progress indicator
605 print 'Copying %s...' % src_uri 662 print 'Copying %s...' % src_uri
606 if dst_uri.names_container(): 663 if dst_uri.names_container():
607 if dst_uri.is_file_uri(): 664 if dst_uri.is_file_uri():
608 # dest names a directory, so append src obj name to dst obj name 665 # dest names a directory, so append src obj name to dst obj name
609 dst_key_name = dst_uri.object_name + os.sep + src_uri.object_name 666 dst_key_name = dst_uri.object_name + os.sep + src_uri.object_name
610 CheckForDirFileConflict(src_uri, dst_key_name) 667 CheckForDirFileConflict(src_uri, dst_key_name)
611 else: 668 else:
612 # dest names a bucket: use src obj name for dst obj name. 669 # dest names a bucket: use src obj name for dst obj name.
613 dst_key_name = src_uri.object_name 670 dst_key_name = src_uri.object_name
614 else: 671 else:
615 # dest is an object or file: use dst obj name 672 # dest is an object or file: use dst obj name
616 dst_key_name = dst_uri.object_name 673 dst_key_name = dst_uri.object_name
617 new_dst_uri = dst_uri.clone_replace_name(dst_key_name) 674 new_dst_uri = dst_uri.clone_replace_name(dst_key_name)
618 PerformCopy(src_uri, new_dst_uri) 675 PerformCopy(src_uri, new_dst_uri, sub_opts, headers)
619 676
620 677
621 def PrintAcl(uri, indent): 678 def HelpCommand(unused_args, unused_sub_opts, unused_headers=None,
622 """Print human-readable info in ACL for given URI. 679 unused_debug=None):
623
624 Args:
625 uri: object- or bucket- granularity StorageUri.
626 indent: string to indent each output line.
627 """
628 acl = uri.get_acl()
629 for grant in acl.acl.grants:
630 if grant.type == 'AmazonCustomerByEmail':
631 grantee = grant.email_address
632 if grant.type == 'CanonicalUser':
633 grantee = grant.id
634 elif grant.type == 'Group':
635 # Extract the group name from the URI (e.g.,
636 # "http://acs.amazonaws.com/groups/global/AuthenticatedUsers")
637 grantee = re.sub('.*/', '', grant.uri)
638 else:
639 OutputAndExit('Unexpected grant type "%s".' % grant.type)
640 print '%s%s: %s' % (indent, grantee, grant.permission)
641
642
643 def HelpCommand(unused_args, unused_sub_opts, unused_debug):
644 """Implementation of help command. 680 """Implementation of help command.
645 681
646 Args: 682 Args:
647 unused_args: command-line arguments 683 unused_args: command-line arguments
648 unused_sub_opts: command-specific options from getopt. 684 unused_sub_opts: command-specific options from getopt.
685 unused_headers: dictionary containing optional HTTP headers to pass to boto.
649 unused_debug: flag indicating whether to include debug output 686 unused_debug: flag indicating whether to include debug output
650 """ 687 """
651 688
652 OutputUsageAndExit() 689 OutputUsageAndExit()
653 690
654 691
655 def PrintObjectMetaData(uri): 692 def PrintObjectMetaData(uri, headers):
656 """Print object metadata. 693 """Print object metadata.
657 694
658 Args: 695 Args:
659 uri: object-granularity StorageUri 696 uri: object-granularity StorageUri
697 headers: dictionary containing optional HTTP headers to pass to boto.
660 """ 698 """
661 print '%s:' % uri 699 print '%s:' % uri
662 key = uri.get_key() 700 key = uri.get_key(False, headers)
663 key.open_read() 701 key.open_read()
664 print '\tObject size:\t%s' % key.size 702 print '\tObject size:\t%s' % key.size
665 print '\tLast mod:\t%s' % key.last_modified 703 print '\tLast mod:\t%s' % key.last_modified
704 if key.cache_control:
705 print '\tCache control:\t%s' % key.cache_control
666 print '\tMIME type:\t%s' % key.content_type 706 print '\tMIME type:\t%s' % key.content_type
707 if key.content_encoding:
708 print '\tContent-Encoding:\t%s' % key.content_encoding
667 print '\tMD5:\t%s' % key.etag.strip('"\'') 709 print '\tMD5:\t%s' % key.etag.strip('"\'')
668 print '\tACL:' % uri 710 print '\tACL:\t%s' % uri.get_acl(False, headers)
669 PrintAcl(uri, '\t\t')
670 711
671 712
672 def ListCommand(args, sub_opts, debug): 713 def ListCommand(args, sub_opts, headers=None, debug=False):
673 """Implementation of ls command. 714 """Implementation of ls command.
674 715
675 Args: 716 Args:
676 args: command-line arguments 717 args: command-line arguments
677 sub_opts: command-specific options from getopt. 718 sub_opts: command-specific options from getopt.
719 headers: dictionary containing optional HTTP headers to pass to boto.
678 debug: flag indicating whether to include debug output 720 debug: flag indicating whether to include debug output
679 """ 721 """
680 722
681 long_listing = False 723 long_listing = False
682 for o, unused_a in sub_opts: 724 for o, unused_a in sub_opts:
683 if o == '-l': 725 if o == '-l':
684 long_listing = True 726 long_listing = True
685 if not args: 727 if not args:
686 # default to listing all gs buckets 728 # default to listing all gs buckets
687 args = ['gs://'] 729 args = ['gs://']
688 730
689 # First expand all URIs into URIs in exp_args, such that: 731 # First expand all URIs into URIs in exp_args, such that:
690 # a) provider-only URIs ('gs://') are left as-is. 732 # a) provider-only URIs ('gs://') are left as-is.
691 # b) bucket-only URIs ('gs://bucket') with -l option are left as bucket-only 733 # b) bucket-only URIs ('gs://bucket') with -l option are left as bucket-only
692 # URIs. 734 # URIs.
693 # c) bucket-only URIs ('gs://bucket') without -l option are replaced by 735 # c) bucket-only URIs ('gs://bucket') without -l option are replaced by
694 # the list of all the objects in the bucket. 736 # the list of all the objects in the bucket.
695 # d) complete URIs ('gs://bucket/obj') are replaced by the list of all of 737 # d) complete URIs ('gs://bucket/obj') are replaced by the list of all of
696 # the matching objects names (handling URIs that contain wildcards, 738 # the matching objects names (handling URIs that contain wildcards,
697 # as well as wildcard-less URIs). 739 # as well as wildcard-less URIs).
698 exp_args = [] 740 exp_args = []
699 for uri_str in args: 741 for uri_str in args:
700 uri = StorageUri(uri_str, False, debug) 742 uri = StorageUri(uri_str, False, debug)
701 if not uri.bucket_name: 743 if not uri.bucket_name:
702 # case a: provider-only URI 744 # case a: provider-only URI
703 exp_args.append(uri_str) 745 exp_args.append(uri_str)
704 elif not uri.object_name: 746 elif not uri.object_name:
705 if long_listing: 747 if long_listing:
706 # case b: bucket-only URI with -l option 748 # case b: bucket-only URI with -l option
707 exp_args.append(uri.__str__()) 749 exp_args.append(uri.__str__())
708 else: 750 else:
709 # case c: bucket-only URI without -l option 751 # case c: bucket-only URI without -l option
710 bucket = uri.get_bucket() 752 bucket = uri.get_bucket(False, headers)
711 for obj in bucket: 753 for obj in bucket:
712 exp_args.append(uri.clone_replace_name(obj.name).uri) 754 exp_args.append(uri.clone_replace_name(obj.name).uri)
713 else: 755 else:
714 # case d: complete URI 756 # case d: complete URI
715 regex = fnmatch.translate(uri.object_name) 757 regex = fnmatch.translate(uri.object_name)
716 bucket = uri.get_bucket() 758 bucket = uri.get_bucket(False, headers)
717 for obj in bucket: 759 for obj in bucket:
718 if re.match(regex, obj.name): 760 if re.match(regex, obj.name):
719 exp_args.append(uri.clone_replace_name(obj.name).uri) 761 exp_args.append(uri.clone_replace_name(obj.name).uri)
720 762
721 # Handle the case of a complete URI that didn't match anything 763 # Handle the case of a complete URI that didn't match anything
722 if uri.names_singleton() and not exp_args: 764 if uri.names_singleton() and not exp_args:
723 ReportNoMatchesAndExit(uri) 765 ReportNoMatchesAndExit(uri)
724 766
725 # Now iterate over all URIs in exp_args and print requested info for each 767 # Now iterate over all URIs in exp_args and print requested info for each
726 for uri_str in exp_args: 768 for uri_str in exp_args:
727 uri = StorageUri(uri_str, False, debug) 769 uri = StorageUri(uri_str, False, debug)
728 if not uri.bucket_name: 770 if not uri.bucket_name:
729 if long_listing: 771 if long_listing:
730 # Provider long listing: print metadata for all buckets 772 # Provider long listing: print metadata for all buckets
731 buckets = uri.get_all_buckets() 773 buckets = uri.get_all_buckets()
732 for bucket in buckets: 774 for bucket in buckets:
733 bucket_uri = StorageUri(uri.provider + '://' + bucket.name, False, 775 bucket_uri = StorageUri(uri.provider + '://' + bucket.name, False,
734 debug) 776 debug)
735 print '%s:\n\tACL:' % bucket_uri 777 print '%s:\n\tACL:\t%s' % (bucket_uri,
736 PrintAcl(bucket_uri, '\t\t') 778 bucket_uri.get_acl(False, headers))
737 else: 779 else:
738 # Provider short listing: list all buckets 780 # Provider short listing: list all buckets
739 buckets = uri.get_all_buckets() 781 buckets = uri.get_all_buckets()
740 for bucket in buckets: 782 for bucket in buckets:
741 bucket_uri = StorageUri(uri.provider + '://' + bucket.name, False, 783 bucket_uri = StorageUri(uri.provider + '://' + bucket.name, False,
742 debug) 784 debug)
743 print bucket_uri 785 print bucket_uri
744 elif not uri.object_name: 786 elif not uri.object_name:
745 if long_listing: 787 if long_listing:
746 # Bucket long listing 788 # Bucket long listing
747 print '%s:\n\tACL:' % uri 789 print '%s:\n\tACL:' % uri
748 PrintAcl(uri, '\t\t') 790 print '\t\t%s' % uri.get_acl(False, headers)
749 else: 791 else:
750 # Bucket short listing 792 # Bucket short listing
751 bucket = uri.get_bucket() 793 bucket = uri.get_bucket(False, headers)
752 for obj in bucket: 794 for obj in bucket:
753 print '%s://%s/%s' % (uri.provider, uri.bucket_name, obj.name) 795 print '%s://%s/%s' % (uri.provider, uri.bucket_name, obj.name)
754 else: 796 else:
755 if long_listing: 797 if long_listing:
756 # Object long listing 798 # Object long listing
757 PrintObjectMetaData(uri) 799 PrintObjectMetaData(uri, headers)
758 else: 800 else:
759 # Object short listing 801 # Object short listing
760 print uri 802 print uri
761 803
762 804
763 def MakeBucketsCommand(args, unused_sub_opts, debug): 805 def MakeBucketsCommand(args, unused_sub_opts, headers=None, debug=False):
764 """Implementation of mb command. 806 """Implementation of mb command.
765 807
766 Args: 808 Args:
767 args: command-line arguments 809 args: command-line arguments
768 unused_sub_opts: command-specific options from getopt. 810 unused_sub_opts: command-specific options from getopt.
811 headers: dictionary containing optional HTTP headers to pass to boto.
769 debug: flag indicating whether to include debug output 812 debug: flag indicating whether to include debug output
770 """ 813 """
771 814
772 for bucket_uri_str in args: 815 for bucket_uri_str in args:
773 bucket_uri = StorageUri(bucket_uri_str, False, debug) 816 bucket_uri = StorageUri(bucket_uri_str, False, debug)
774 if len(args) > 1: 817 if len(args) > 1:
775 # Progress indicator 818 # Progress indicator
776 print 'Creating %s...' % bucket_uri 819 print 'Creating %s...' % bucket_uri
777 bucket_uri.create_bucket() 820 bucket_uri.create_bucket(headers)
778 821
779 822
780 def MoveObjsCommand(args, sub_opts, debug): 823 def MoveObjsCommand(args, sub_opts, headers=None, debug=False):
781 """Implementation of mv command. 824 """Implementation of mv command.
782 825
783 Note that there is no atomic rename operation - this command is simply 826 Note that there is no atomic rename operation - this command is simply
784 a shorthand for 'cp' followed by 'rm'. 827 a shorthand for 'cp' followed by 'rm'.
785 828
786 Args: 829 Args:
787 args: command-line arguments 830 args: command-line arguments
788 sub_opts: command-specific options from getopt. 831 sub_opts: command-specific options from getopt.
832 headers: dictionary containing optional HTTP headers to pass to boto.
789 debug: flag indicating whether to include debug output 833 debug: flag indicating whether to include debug output
790 """ 834 """
791 835
792 # Refuse to delete a bucket or directory src URI (force users to explicitly 836 # Refuse to delete a bucket or directory src URI (force users to explicitly
793 # do that as a separate operation). 837 # do that as a separate operation).
794 src_uri_to_check = StorageUri(args[0]) 838 src_uri_to_check = StorageUri(args[0])
795 if src_uri_to_check.names_container(): 839 if src_uri_to_check.names_container():
796 OutputAndExit('Will not remove source buckets or directories. You should ' 840 OutputAndExit('Will not remove source buckets or directories. You should '
797 'separately copy and remove for that purpose.') 841 'separately copy and remove for that purpose.')
798 842
799 if len(args) > 2: 843 if len(args) > 2:
800 InsistUriNamesContainer('mv', StorageUri(args[-1])) 844 InsistUriNamesContainer('mv', StorageUri(args[-1]))
801 845
802 CopyObjsCommand(args, sub_opts, debug) 846 CopyObjsCommand(args, sub_opts, headers, debug)
803 RemoveObjsCommand(args[0:1], sub_opts, debug) 847 RemoveObjsCommand(args[0:1], sub_opts, headers, debug)
804 848
805 849
806 def RemoveBucketsCommand(args, unused_sub_opts, debug): 850 def RemoveBucketsCommand(args, unused_sub_opts, headers=None, debug=False):
807 """Implementation of rb command. 851 """Implementation of rb command.
808 852
809 Args: 853 Args:
810 args: command-line arguments 854 args: command-line arguments
811 unused_sub_opts: command-specific options from getopt. 855 unused_sub_opts: command-specific options from getopt.
856 headers: dictionary containing optional HTTP headers to pass to boto.
812 debug: flag indicating whether to include debug output 857 debug: flag indicating whether to include debug output
813 """ 858 """
814 859
815 for bucket_uri_str in args: 860 for bucket_uri_str in args:
816 bucket_uri = StorageUri(bucket_uri_str, True, debug) 861 bucket_uri = StorageUri(bucket_uri_str, True, debug)
817 if len(args) > 1: 862 if len(args) > 1:
818 # Progress indicator 863 # Progress indicator
819 print 'Removing %s...' % bucket_uri 864 print 'Removing %s...' % bucket_uri
820 bucket_uri.delete_bucket() 865 bucket_uri.delete_bucket(headers)
821 866
822 867
823 def RemoveObjsCommand(args, unused_sub_opts, debug): 868 def RemoveObjsCommand(args, unused_sub_opts, headers=None, debug=False):
824 """Implementation of rm command. 869 """Implementation of rm command.
825 870
826 Args: 871 Args:
827 args: command-line arguments 872 args: command-line arguments
828 unused_sub_opts: command-specific options from getopt. 873 unused_sub_opts: command-specific options from getopt.
874 headers: dictionary containing optional HTTP headers to pass to boto.
829 debug: flag indicating whether to include debug output 875 debug: flag indicating whether to include debug output
830 """ 876 """
831 877
832 # Expand object name globs, if any. 878 # Expand object name globs, if any.
833 exp_args = [] 879 exp_uris = []
834 for uri_str in args: 880 for uri_str in args:
835 for exp_uri_str in ExpandStorageUriGlob(uri_str, debug): 881 for exp_uri in ExpandStorageUriGlob(uri_str, headers, debug):
836 exp_args.append(exp_uri_str) 882 exp_uris.append(exp_uri)
837 for uri_str in exp_args: 883 for uri in exp_uris:
838 uri = StorageUri(uri_str, False, debug)
839 if uri.names_container(): 884 if uri.names_container():
840 OutputAndExit('"rm" command will not remove buckets. To delete this ' 885 OutputAndExit('"rm" command will not remove buckets. To delete this '
841 'bucket do:\n\tgsutil rm %s/*\n\tgsutil rb %s' % 886 'bucket do:\n\tgsutil rm %s/*\n\tgsutil rb %s' %
842 (uri.uri, uri.uri)) 887 (uri.uri, uri.uri))
843 if len(exp_args) > 1: 888 if len(exp_uris) > 1:
844 # Progress indicator 889 # Progress indicator
845 print 'Removing %s...' % uri 890 print 'Removing %s...' % uri
846 uri.delete_key() 891 uri.delete_key(False, headers)
847 892
848 893
849 def HaveFileUris(args): 894 def HaveFileUris(args):
850 """Checks whether args contains any file URIs. 895 """Checks whether args contains any file URIs.
851 896
852 Args: 897 Args:
853 args: command-line arguments 898 args: command-line arguments
854 899
855 Returns: 900 Returns:
856 True if args contains any file URIs. 901 True if args contains any file URIs.
857 """ 902 """
858 for uri_str in args: 903 for uri_str in args:
859 if uri_str.lower().startswith('file://') or uri_str.find(':') == -1: 904 if uri_str.lower().startswith('file://') or uri_str.find(':') == -1:
860 return True 905 return True
861 return False 906 return False
862 907
863 908
864 def HaveProviderUris(args): 909 def HaveProviderUris(args):
865 """Checks whether args contains any provider URIs (like 'gs://'). 910 """Checks whether args contains any provider URIs (like 'gs://').
866 911
867 Args: 912 Args:
868 args: command-line arguments 913 args: command-line arguments
869 914
870 Returns: 915 Returns:
871 True if args contains any provider URIs. 916 True if args contains any provider URIs.
872 """ 917 """
873 for uri_str in args: 918 for uri_str in args:
874 if re.match('^[a-z]+://$', uri_str): 919 if re.match('^[a-z]+://$', uri_str):
875 return True 920 return True
876 return False 921 return False
877 922
878 923
879 def GetBotoConfigFileList(): 924 def GetBotoConfigFileList():
880 """Returns list of boto config files that exist.""" 925 """Returns list of boto config files that exist."""
881 926
882 config_paths = boto.pyami.config.BotoConfigLocations 927 config_paths = boto.pyami.config.BotoConfigLocations
883 if 'AWS_CREDENTIAL_FILE' in os.environ: 928 if 'AWS_CREDENTIAL_FILE' in os.environ:
884 config_paths.append(os.environ['AWS_CREDENTIAL_FILE']) 929 config_paths.append(os.environ['AWS_CREDENTIAL_FILE'])
885 config_files = {} 930 config_files = {}
886 for config_path in config_paths: 931 for config_path in config_paths:
887 if os.path.exists(config_path): 932 if os.path.exists(config_path):
888 config_files[config_path] = 1 933 config_files[config_path] = 1
889 cf_list = [] 934 cf_list = []
890 for config_file in config_files: 935 for config_file in config_files:
891 cf_list.append(config_file) 936 cf_list.append(config_file)
892 return cf_list 937 return cf_list
893 938
894 939
895 prelude_config_content = ( 940 prelude_config_content = (
896 """# This file contains credentials and other configuration information needed 941 """# This file contains credentials and other configuration information needed
897 # by the boto library, used by gsutil. You can edit this file (e.g., to add 942 # by the boto library, used by gsutil. You can edit this file (e.g., to add
898 # credentials) but be careful not to mis-edit any of the variable names (like 943 # credentials) but be careful not to mis-edit any of the variable names (like
899 # "gs_access_key_id") or remove important markers (like the "[Credentials]" and 944 # "gs_access_key_id") or remove important markers (like the "[Credentials]" and
900 # "[Boto]" section delimeters). 945 # "[Boto]" section delimeters).
901 # 946 #
902 """) 947 """)
903 948
904 additional_config_content = """ 949 additional_config_content = """
905 950
906 [Boto] 951 [Boto]
907 952
908 # To use a proxy, edit and uncomment the proxy and proxy_port lines. If you 953 # To use a proxy, edit and uncomment the proxy and proxy_port lines. If you
909 # need a user/password with this proxy, edit and uncomment those lines as well. 954 # need a user/password with this proxy, edit and uncomment those lines as well.
910 #proxy = <proxy host> 955 #proxy = <proxy host>
911 #proxy_port = <proxy port> 956 #proxy_port = <proxy port>
912 #proxy_user = <your proxy user name> 957 #proxy_user = <your proxy user name>
913 #proxy_pass = <your proxy password> 958 #proxy_pass = <your proxy password>
914 959
915 # Set 'is_secure' to False to cause boto to connect using HTTP instead of the 960 # Set 'is_secure' to False to cause boto to connect using HTTP instead of the
916 # default HTTPS. This is useful if you want to capture/analyze traffic 961 # default HTTPS. This is useful if you want to capture/analyze traffic
917 # (e.g., with tcpdump). 962 # (e.g., with tcpdump).
918 #is_secure = False 963 #is_secure = False
919 964
920 # 'debug' controls the level of debug messages printed: 0 for none, 1 965 # 'debug' controls the level of debug messages printed: 0 for none, 1
921 # for basic boto debug, 2 for all boto debug plus HTTP requests/responses. 966 # for basic boto debug, 2 for all boto debug plus HTTP requests/responses.
922 # Note: 'gsutil -d' sets debug to 2 for that one command run. 967 # Note: 'gsutil -d' sets debug to 2 for that one command run.
923 #debug = <0, 1, or 2> 968 #debug = <0, 1, or 2>
924 969
925 # 'num_retries' controls the number of retry attempts made when errors occur. 970 # 'num_retries' controls the number of retry attempts made when errors occur.
926 # The default is 5. 971 # The default is 5.
927 #num_retries = <integer value> 972 #num_retries = <integer value>
928 """ 973 """
929 974
930 975
931 def CreateBotoConfigFile(): 976 def CreateBotoConfigFile():
932 """Creates a boto config file interactively, containing needed credentials.""" 977 """Creates a boto config file interactively, containing needed credentials."""
933 978
934 if 'HOME' in os.environ: 979 if 'HOME' in os.environ:
935 config_path = ('%s%s.boto' % (os.environ['HOME'], os.sep)) 980 config_path = ('%s%s.boto' % (os.environ['HOME'], os.sep))
936 else: 981 else:
937 config_path = ('.%s.boto' % os.sep) 982 config_path = ('.%s.boto' % os.sep)
938 sys.stderr.write('You have no boto config file. This script will create ' 983 sys.stderr.write('You have no boto config file. This script will create '
939 'one at\n%s\ncontaining your credentials, based on your ' 984 'one at\n%s\ncontaining your credentials, based on your '
940 'responses to the following questions.\n\n' % config_path) 985 'responses to the following questions.\n\n' % config_path)
941 986
942 provider_map = {'Amazon': 'aws', 'Google': 'gs'} 987 provider_map = {'Amazon': 'aws', 'Google': 'gs'}
943 uri_map = {'Amazon': 's3', 'Google': 'gs'} 988 uri_map = {'Amazon': 's3', 'Google': 'gs'}
944 key_ids = {} 989 key_ids = {}
945 sec_keys = {} 990 sec_keys = {}
946 got_creds = False 991 got_creds = False
947 for provider in provider_map: 992 for provider in provider_map:
948 if provider == 'Google': 993 if provider == 'Google':
949 key_ids[provider] = raw_input('What is your %s access key ID? ' % 994 key_ids[provider] = raw_input('What is your %s access key ID? ' %
950 provider) 995 provider)
951 sec_keys[provider] = raw_input('What is your %s secret access key? ' % 996 sec_keys[provider] = raw_input('What is your %s secret access key? ' %
952 provider) 997 provider)
953 got_creds = True 998 got_creds = True
954 if not key_ids[provider] or not sec_keys[provider]: 999 if not key_ids[provider] or not sec_keys[provider]:
955 OutputAndExit('Incomplete credentials provided. Please try again.') 1000 OutputAndExit('Incomplete credentials provided. Please try again.')
956 if not got_creds: 1001 if not got_creds:
957 OutputAndExit('No credentials provided. Please try again.') 1002 OutputAndExit('No credentials provided. Please try again.')
958 cfp = open(config_path, 'w') 1003 cfp = open(config_path, 'w')
959 if not cfp: 1004 if not cfp:
960 OutputAndExit('Unable to write "%s".' % config_path) 1005 OutputAndExit('Unable to write "%s".' % config_path)
961 os.chmod(config_path, stat.S_IRUSR | stat.S_IWUSR) 1006 os.chmod(config_path, stat.S_IRUSR | stat.S_IWUSR)
962 cfp.write(prelude_config_content) 1007 cfp.write(prelude_config_content)
963 cfp.write('# This file was created by gsutil version "%s"\n# at %s.\n\n\n' 1008 cfp.write('# This file was created by gsutil version "%s"\n# at %s.\n\n\n'
964 % (LoadVersionString(), 1009 % (LoadVersionString(),
965 datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'))) 1010 datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')))
966 cfp.write('[Credentials]\n\n') 1011 cfp.write('[Credentials]\n\n')
967 for provider in provider_map: 1012 for provider in provider_map:
968 prefix = provider_map[provider] 1013 prefix = provider_map[provider]
969 uri_scheme = uri_map[provider] 1014 uri_scheme = uri_map[provider]
970 if provider in key_ids and provider in sec_keys: 1015 if provider in key_ids and provider in sec_keys:
971 cfp.write('# %s credentials ("%s://" URIs):\n' % 1016 cfp.write('# %s credentials ("%s://" URIs):\n' %
972 (provider, uri_scheme)) 1017 (provider, uri_scheme))
973 cfp.write('%s_access_key_id = %s\n' % (prefix, key_ids[provider])) 1018 cfp.write('%s_access_key_id = %s\n' % (prefix, key_ids[provider]))
974 cfp.write('%s_secret_access_key = %s\n' % (prefix, sec_keys[provider])) 1019 cfp.write('%s_secret_access_key = %s\n' % (prefix, sec_keys[provider]))
975 else: 1020 else:
976 cfp.write('# To add %s credentials ("%s://" URIs), edit and ' 1021 cfp.write('# To add %s credentials ("%s://" URIs), edit and '
977 'uncomment the\n# following two lines:\n' 1022 'uncomment the\n# following two lines:\n'
978 '#%s_access_key_id = <your %s access key ID>\n' 1023 '#%s_access_key_id = <your %s access key ID>\n'
979 '#%s_secret_access_key = <your %s secret access key>\n' % 1024 '#%s_secret_access_key = <your %s secret access key>\n' %
980 (provider, uri_scheme, prefix, provider, prefix, provider)) 1025 (provider, uri_scheme, prefix, provider, prefix, provider))
981 cfp.write('# The ability to specify an alternate storage host is primarily ' 1026 cfp.write('# The ability to specify an alternate storage host is primarily '
982 'for developers.\n' 1027 'for developers.\n'
983 '#%s_host = <alternate storage host address>\n\n' % (prefix)) 1028 '#%s_host = <alternate storage host address>\n\n' % (prefix))
984 cfp.write(additional_config_content) 1029 cfp.write(additional_config_content)
985 cfp.close() 1030 cfp.close()
986 OutputAndExit('\nConfiguration file "%s" created. If you need to use\na ' 1031 OutputAndExit('\nConfiguration file "%s" created. If you need to use\na '
987 'proxy to access the Internet please see the instructions in ' 1032 'proxy to access the Internet please see the instructions in '
988 'that file.\nPlease try running gsutil again now.' % 1033 'that file.\nPlease try running gsutil again now.' %
989 config_path) 1034 config_path)
990 1035
991 1036
992 def SetupConfigIfNeeded(): 1037 def SetupConfigIfNeeded():
993 """Interactively creates boto credential/config file if needed.""" 1038 """Interactively creates boto credential/config file if needed."""
994 1039
995 config = boto.config 1040 config = boto.config
996 has_goog_creds = (config.has_option('Credentials', 'gs_access_key_id') and 1041 has_goog_creds = (config.has_option('Credentials', 'gs_access_key_id') and
997 config.has_option('Credentials', 'gs_secret_access_key')) 1042 config.has_option('Credentials', 'gs_secret_access_key'))
998 has_amzn_creds = (config.has_option('Credentials', 'aws_access_key_id') and 1043 has_amzn_creds = (config.has_option('Credentials', 'aws_access_key_id') and
999 config.has_option('Credentials', 'aws_secret_access_key')) 1044 config.has_option('Credentials', 'aws_secret_access_key'))
1000 if not has_goog_creds and not has_amzn_creds: 1045 if not has_goog_creds and not has_amzn_creds:
1001 config_file_list = GetBotoConfigFileList() 1046 config_file_list = GetBotoConfigFileList()
1002 if config_file_list: 1047 if config_file_list:
1003 OutputAndExit('You have no storage service credentials in any of the ' 1048 OutputAndExit('You have no storage service credentials in any of the '
1004 'following boto config\nfiles. Please add your credentials ' 1049 'following boto config\nfiles. Please add your credentials '
1005 'as described in the gsutil README file, or else\ndelete' 1050 'as described in the gsutil README file, or else\ndelete'
1006 'these files and re-run this script to re-create them:\n%s' 1051 'these files and re-run this script to re-create them:\n%s'
1007 % config_file_list) 1052 % config_file_list)
1008 else: 1053 else:
1009 CreateBotoConfigFile() 1054 CreateBotoConfigFile()
1010 1055
1011 1056
1012 # Maps command name to 1057 # Maps command name to
1013 # [function, min_args, max_args, supported_sub_args, file_uri_ok, 1058 # [function, min_args, max_args, supported_sub_args, file_uri_ok,
1014 # provider_uri_ok, uris_start_arg] 1059 # provider_uri_ok, uris_start_arg]
1015 NO_MAX = sys.maxint 1060 NO_MAX = sys.maxint
1016 commands = { 1061 commands = {
1017 'cat': [CatCommand, 0, NO_MAX, 'h', False, True, 0], 1062 'cat': [CatCommand, 0, NO_MAX, 'h', False, True, 0],
1018 'cp': [CopyObjsCommand, 2, NO_MAX, '', True, False, 0], 1063 'cp': [CopyObjsCommand, 2, NO_MAX, 'a:tz:', True, False, 0],
1019 'getacl': [GetAclCommand, 1, 1, '', False, False, 0], 1064 'getacl': [GetAclCommand, 1, 1, '', False, False, 0],
1020 'help': [HelpCommand, 0, 0, '', False, False, 0], 1065 'help': [HelpCommand, 0, 0, '', False, False, 0],
1021 'ls': [ListCommand, 0, NO_MAX, 'l', False, True, 0], 1066 'ls': [ListCommand, 0, NO_MAX, 'l', False, True, 0],
1022 'mb': [MakeBucketsCommand, 1, NO_MAX, '', False, False, 0], 1067 'mb': [MakeBucketsCommand, 1, NO_MAX, '', False, False, 0],
1023 'mv': [MoveObjsCommand, 2, NO_MAX, '', True, False, 0], 1068 'mv': [MoveObjsCommand, 2, NO_MAX, '', True, False, 0],
1024 'rb': [RemoveBucketsCommand, 1, NO_MAX, '', False, False, 0], 1069 'rb': [RemoveBucketsCommand, 1, NO_MAX, '', False, False, 0],
1025 'rm': [RemoveObjsCommand, 1, NO_MAX, '', False, False, 0], 1070 'rm': [RemoveObjsCommand, 1, NO_MAX, '', False, False, 0],
1026 'setacl': [SetAclCommand, 2, NO_MAX, '', False, False, 1], 1071 'setacl': [SetAclCommand, 2, NO_MAX, '', False, False, 1],
1027 'update': [UpdateCommand, 0, 0, 'f', False, False, 0] 1072 'update': [UpdateCommand, 0, 0, 'f', False, False, 0]
1028 } 1073 }
1029 1074
1030 1075
1031 def main(): 1076 def main():
1032 if float('%d.%d%d' 1077 if float('%d.%d%d'
1033 %(sys.version_info[0], sys.version_info[1], sys.version_info[2]) 1078 %(sys.version_info[0], sys.version_info[1], sys.version_info[2])
1034 ) < 2.51: 1079 ) < 2.51:
1035 OutputAndExit('This tool requires Python 2.5.1 or higher.') 1080 OutputAndExit('This tool requires Python 2.5.1 or higher.')
1036 1081
1037 # If user enters no commands just print the usage info. 1082 # If user enters no commands just print the usage info.
1038 if len(sys.argv) == 1: 1083 if len(sys.argv) == 1:
1039 OutputUsageAndExit() 1084 OutputUsageAndExit()
1040 try: 1085 try:
1041 opts, args = getopt.getopt(sys.argv[1:], 'dh', ['debug', 'help']) 1086 opts, args = getopt.getopt(sys.argv[1:], 'd?h:',
1042 if len(args) == 0: 1087 ['debug', 'help', 'header'])
1088 if not args:
1043 command = 'help' 1089 command = 'help'
1044 else: 1090 else:
1045 command = args[0] 1091 command = args[0]
1046 if command not in commands: 1092 if command not in commands:
1047 OutputAndExit('Invalid command "%s".' % command) 1093 OutputAndExit('Invalid command "%s".' % command)
1048 command_info = commands[command] 1094 command_info = commands[command]
1049 command_func = command_info[0] 1095 command_func = command_info[0]
1050 min_arg_count = command_info[1] 1096 min_arg_count = command_info[1]
1051 max_arg_count = command_info[2] 1097 max_arg_count = command_info[2]
1052 supported_sub_args = command_info[3] 1098 supported_sub_args = command_info[3]
1053 file_uri_ok = command_info[4] 1099 file_uri_ok = command_info[4]
1054 provider_uri_ok = command_info[5] 1100 provider_uri_ok = command_info[5]
1055 uris_start_arg = command_info[6] 1101 uris_start_arg = command_info[6]
1056 sub_opts, args = getopt.getopt(args[1:], supported_sub_args) 1102 sub_opts, args = getopt.getopt(args[1:], supported_sub_args)
1057 if len(args) < min_arg_count or len(args) > max_arg_count: 1103 if len(args) < min_arg_count or len(args) > max_arg_count:
1058 OutputAndExit('Wrong number of arguments for "%s" command.' % command) 1104 OutputAndExit('Wrong number of arguments for "%s" command.' % command)
1059 if not file_uri_ok and HaveFileUris(args[uris_start_arg:]): 1105 if not file_uri_ok and HaveFileUris(args[uris_start_arg:]):
1060 OutputAndExit('"%s" command does not support "file://" URIs.' % command) 1106 OutputAndExit('"%s" command does not support "file://" URIs.' % command)
1061 if not provider_uri_ok and HaveProviderUris(args[uris_start_arg:]): 1107 if not provider_uri_ok and HaveProviderUris(args[uris_start_arg:]):
1062 OutputAndExit('"%s" command does not support provider-only URIs.' % 1108 OutputAndExit('"%s" command does not support provider-only URIs.' %
1063 command) 1109 command)
1064 except InvalidUriError, e: 1110 except InvalidUriError, e:
1065 OutputAndExit(e.message) 1111 OutputAndExit(e.message)
1066 except getopt.GetoptError, e: 1112 except getopt.GetoptError, e:
1067 OutputAndExit(e.msg) 1113 OutputAndExit(e.msg)
1068 1114
1069 SetupConfigIfNeeded() 1115 SetupConfigIfNeeded()
1070 1116
1071 debug = 0 1117 debug = 0
1072 for o, unused_a in opts: 1118 headers = {}
1119 for o, a in opts:
1073 if o in ('-d', '--debug'): 1120 if o in ('-d', '--debug'):
1074 # Debug level 2 causes httplib output plus boto debug output 1121 # Debug level 2 causes httplib output plus boto debug output
1075 debug = 2 1122 debug = 2
1076 if o in ('-h', '--help'): 1123 if o in ('-?', '--help'):
1077 OutputUsageAndExit() 1124 OutputUsageAndExit()
1125 if o in ('-h', '--header'):
1126 (hdr_name, unused_ptn, hdr_val) = a.partition(':')
1127 if not hdr_name or not hdr_val:
1128 OutputUsageAndExit()
1129 headers[hdr_name] = hdr_val
1078 1130
1079 try: 1131 try:
1080 command_func(args, sub_opts, debug) 1132 command_func(args, sub_opts, headers, debug)
1081 except BotoClientError, e: 1133 except BotoClientError, e:
1082 OutputAndExit('Failure: %s.' % e.reason) 1134 OutputAndExit('BotoClientError,: %s.' % e.reason)
1083 except boto.exception.InvalidUriError, e: 1135 except InvalidAclError, e:
1084 OutputAndExit('Failure: %s.' % e.message) 1136 OutputAndExit('InvalidAclError: %s.' % e.message)
1137 except InvalidUriError, e:
1138 OutputAndExit('InvalidUriError: %s.' % e.message)
1085 except S3ResponseError, e: 1139 except S3ResponseError, e:
1086 detail_start = e.body.find('<Details>') 1140 detail_start = e.body.find('<Details>')
1087 detail_end = e.body.find('</Details>') 1141 detail_end = e.body.find('</Details>')
1088 if detail_start != -1 and detail_end != -1: 1142 if detail_start != -1 and detail_end != -1:
1089 detail = e.body[detail_start+9:detail_end] 1143 detail = e.body[detail_start+9:detail_end]
1090 OutputAndExit('Failure: status=%d, code=%s, reason=%s, detail=%s.' % 1144 OutputAndExit('S3ResponseError: status=%d, code=%s, reason=%s, detail=%s.'
1091 (e.status, e.code, e.reason, detail)) 1145 % (e.status, e.code, e.reason, detail))
1092 else: 1146 else:
1093 OutputAndExit('Failure: status=%d, code=%s, reason=%s.' % 1147 OutputAndExit('S3ResponseError:: status=%d, code=%s, reason=%s.' %
1094 (e.status, e.code, e.reason)) 1148 (e.status, e.code, e.reason))
1095 except AttributeError, e: 1149 except AttributeError, e:
1096 if e.message.find('aws_secret_access_key') != -1: 1150 if e.message.find('aws_secret_access_key') != -1:
1097 OutputAndExit('Missing credentials for the given URI(s). Does your ' 1151 OutputAndExit('Missing credentials for the given URI(s). Does your '
1098 'boto config file contain all needed credentials?') 1152 'boto config file contain all needed credentials?')
1099 else: 1153 else:
1100 OutputAndExit(e.message) 1154 OutputAndExit(e.message)
1101 except Exception, e: 1155 except Exception, e:
1102 OutputAndExit('Failure: %s.' % e) 1156 OutputAndExit('Failure: %s.' % e)
1103 1157
1104 if __name__ == '__main__': 1158 if __name__ == '__main__':
1105 main() 1159 main()
1106 sys.exit(0) 1160 sys.exit(0)
Powered by Google Project Hosting