comparison planemo/bin/s3put @ 0:d30785e31577 draft

"planemo upload commit 6eee67778febed82ddd413c3ca40b3183a3898f1"
author guerler
date Fri, 31 Jul 2020 00:18:57 -0400
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:d30785e31577
1 #!/Users/guerler/spring/springsuite/planemo/bin/python3
2 # Copyright (c) 2006,2007,2008 Mitch Garnaat http://garnaat.org/
3 #
4 # Permission is hereby granted, free of charge, to any person obtaining a
5 # copy of this software and associated documentation files (the
6 # "Software"), to deal in the Software without restriction, including
7 # without limitation the rights to use, copy, modify, merge, publish, dis-
8 # tribute, sublicense, and/or sell copies of the Software, and to permit
9 # persons to whom the Software is furnished to do so, subject to the fol-
10 # lowing conditions:
11 #
12 # The above copyright notice and this permission notice shall be included
13 # in all copies or substantial portions of the Software.
14 #
15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16 # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
17 # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
18 # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
19 # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 # IN THE SOFTWARE.
22 #
23 import getopt
24 import sys
25 import os
26 import boto
27
28 from boto.compat import six
29
30 try:
31 # multipart portions copyright Fabian Topfstedt
32 # https://gist.github.com/924094
33
34 import math
35 import mimetypes
36 from multiprocessing import Pool
37 from boto.s3.connection import S3Connection
38 from filechunkio import FileChunkIO
39 multipart_capable = True
40 usage_flag_multipart_capable = """ [--multipart]"""
41 usage_string_multipart_capable = """
42 multipart - Upload files as multiple parts. This needs filechunkio.
43 Requires ListBucket, ListMultipartUploadParts,
44 ListBucketMultipartUploads and PutObject permissions."""
45 except ImportError as err:
46 multipart_capable = False
47 usage_flag_multipart_capable = ""
48 if six.PY2:
49 attribute = 'message'
50 else:
51 attribute = 'msg'
52 usage_string_multipart_capable = '\n\n "' + \
53 getattr(err, attribute)[len('No module named '):] + \
54 '" is missing for multipart support '
55
56
57 DEFAULT_REGION = 'us-east-1'
58
59 usage_string = """
60 SYNOPSIS
61 s3put [-a/--access_key <access_key>] [-s/--secret_key <secret_key>]
62 -b/--bucket <bucket_name> [-c/--callback <num_cb>]
63 [-d/--debug <debug_level>] [-i/--ignore <ignore_dirs>]
64 [-n/--no_op] [-p/--prefix <prefix>] [-k/--key_prefix <key_prefix>]
65 [-q/--quiet] [-g/--grant grant] [-w/--no_overwrite] [-r/--reduced]
66 [--header] [--region <name>] [--host <s3_host>]""" + \
67 usage_flag_multipart_capable + """ path [path...]
68
69 Where
70 access_key - Your AWS Access Key ID. If not supplied, boto will
71 use the value of the environment variable
72 AWS_ACCESS_KEY_ID
73 secret_key - Your AWS Secret Access Key. If not supplied, boto
74 will use the value of the environment variable
75 AWS_SECRET_ACCESS_KEY
76 bucket_name - The name of the S3 bucket the file(s) should be
77 copied to.
78 path - A path to a directory or file that represents the items
79 to be uploaded. If the path points to an individual file,
80 that file will be uploaded to the specified bucket. If the
81 path points to a directory, it will recursively traverse
82 the directory and upload all files to the specified bucket.
83 debug_level - 0 means no debug output (default), 1 means normal
84 debug output from boto, and 2 means boto debug output
85 plus request/response output from httplib
86 ignore_dirs - a comma-separated list of directory names that will
87 be ignored and not uploaded to S3.
88 num_cb - The number of progress callbacks to display. The default
89 is zero which means no callbacks. If you supplied a value
90 of "-c 10" for example, the progress callback would be
91 called 10 times for each file transferred.
92 prefix - A file path prefix that will be stripped from the full
93 path of the file when determining the key name in S3.
94 For example, if the full path of a file is:
95 /home/foo/bar/fie.baz
96 and the prefix is specified as "-p /home/foo/" the
97 resulting key name in S3 will be:
98 /bar/fie.baz
99 The prefix must end in a trailing separator and if it
100 does not then one will be added.
101 key_prefix - A prefix to be added to the S3 key name, after any
102 stripping of the file path is done based on the
103 "-p/--prefix" option.
104 reduced - Use Reduced Redundancy storage
105 grant - A canned ACL policy that will be granted on each file
106 transferred to S3. The value of provided must be one
107 of the "canned" ACL policies supported by S3:
108 private|public-read|public-read-write|authenticated-read
109 no_overwrite - No files will be overwritten on S3, if the file/key
110 exists on s3 it will be kept. This is useful for
111 resuming interrupted transfers. Note this is not a
112 sync, even if the file has been updated locally if
113 the key exists on s3 the file on s3 will not be
114 updated.
115 header - key=value pairs of extra header(s) to pass along in the
116 request
117 region - Manually set a region for buckets that are not in the US
118 classic region. Normally the region is autodetected, but
119 setting this yourself is more efficient.
120 host - Hostname override, for using an endpoint other then AWS S3
121 """ + usage_string_multipart_capable + """
122
123
124 If the -n option is provided, no files will be transferred to S3 but
125 informational messages will be printed about what would happen.
126 """
127
128
129 def usage(status=1):
130 print(usage_string)
131 sys.exit(status)
132
133
134 def submit_cb(bytes_so_far, total_bytes):
135 print('%d bytes transferred / %d bytes total' % (bytes_so_far, total_bytes))
136
137
138 def get_key_name(fullpath, prefix, key_prefix):
139 if fullpath.startswith(prefix):
140 key_name = fullpath[len(prefix):]
141 else:
142 key_name = fullpath
143 l = key_name.split(os.sep)
144 return key_prefix + '/'.join(l)
145
146
147 def _upload_part(bucketname, aws_key, aws_secret, multipart_id, part_num,
148 source_path, offset, bytes, debug, cb, num_cb,
149 amount_of_retries=10):
150 """
151 Uploads a part with retries.
152 """
153 if debug == 1:
154 print("_upload_part(%s, %s, %s)" % (source_path, offset, bytes))
155
156 def _upload(retries_left=amount_of_retries):
157 try:
158 if debug == 1:
159 print('Start uploading part #%d ...' % part_num)
160 conn = S3Connection(aws_key, aws_secret)
161 conn.debug = debug
162 bucket = conn.get_bucket(bucketname)
163 for mp in bucket.get_all_multipart_uploads():
164 if mp.id == multipart_id:
165 with FileChunkIO(source_path, 'r', offset=offset,
166 bytes=bytes) as fp:
167 mp.upload_part_from_file(fp=fp, part_num=part_num,
168 cb=cb, num_cb=num_cb)
169 break
170 except Exception as exc:
171 if retries_left:
172 _upload(retries_left=retries_left - 1)
173 else:
174 print('Failed uploading part #%d' % part_num)
175 raise exc
176 else:
177 if debug == 1:
178 print('... Uploaded part #%d' % part_num)
179
180 _upload()
181
182 def check_valid_region(conn, region):
183 if conn is None:
184 print('Invalid region (%s)' % region)
185 sys.exit(1)
186
187 def multipart_upload(bucketname, aws_key, aws_secret, source_path, keyname,
188 reduced, debug, cb, num_cb, acl='private', headers={},
189 guess_mimetype=True, parallel_processes=4,
190 region=DEFAULT_REGION):
191 """
192 Parallel multipart upload.
193 """
194 conn = boto.s3.connect_to_region(region, aws_access_key_id=aws_key,
195 aws_secret_access_key=aws_secret)
196 check_valid_region(conn, region)
197 conn.debug = debug
198 bucket = conn.get_bucket(bucketname)
199
200 if guess_mimetype:
201 mtype = mimetypes.guess_type(keyname)[0] or 'application/octet-stream'
202 headers.update({'Content-Type': mtype})
203
204 mp = bucket.initiate_multipart_upload(keyname, headers=headers,
205 reduced_redundancy=reduced)
206
207 source_size = os.stat(source_path).st_size
208 bytes_per_chunk = max(int(math.sqrt(5242880) * math.sqrt(source_size)),
209 5242880)
210 chunk_amount = int(math.ceil(source_size / float(bytes_per_chunk)))
211
212 pool = Pool(processes=parallel_processes)
213 for i in range(chunk_amount):
214 offset = i * bytes_per_chunk
215 remaining_bytes = source_size - offset
216 bytes = min([bytes_per_chunk, remaining_bytes])
217 part_num = i + 1
218 pool.apply_async(_upload_part, [bucketname, aws_key, aws_secret, mp.id,
219 part_num, source_path, offset, bytes,
220 debug, cb, num_cb])
221 pool.close()
222 pool.join()
223
224 if len(mp.get_all_parts()) == chunk_amount:
225 mp.complete_upload()
226 key = bucket.get_key(keyname)
227 key.set_acl(acl)
228 else:
229 mp.cancel_upload()
230
231
232 def singlepart_upload(bucket, key_name, fullpath, *kargs, **kwargs):
233 """
234 Single upload.
235 """
236 k = bucket.new_key(key_name)
237 k.set_contents_from_filename(fullpath, *kargs, **kwargs)
238
239
240 def expand_path(path):
241 path = os.path.expanduser(path)
242 path = os.path.expandvars(path)
243 return os.path.abspath(path)
244
245
246 def main():
247
248 # default values
249 aws_access_key_id = None
250 aws_secret_access_key = None
251 bucket_name = ''
252 ignore_dirs = []
253 debug = 0
254 cb = None
255 num_cb = 0
256 quiet = False
257 no_op = False
258 prefix = '/'
259 key_prefix = ''
260 grant = None
261 no_overwrite = False
262 reduced = False
263 headers = {}
264 host = None
265 multipart_requested = False
266 region = None
267
268 try:
269 opts, args = getopt.getopt(
270 sys.argv[1:], 'a:b:c::d:g:hi:k:np:qs:wr',
271 ['access_key=', 'bucket=', 'callback=', 'debug=', 'help', 'grant=',
272 'ignore=', 'key_prefix=', 'no_op', 'prefix=', 'quiet',
273 'secret_key=', 'no_overwrite', 'reduced', 'header=', 'multipart',
274 'host=', 'region='])
275 except:
276 usage(1)
277
278 # parse opts
279 for o, a in opts:
280 if o in ('-h', '--help'):
281 usage(0)
282 if o in ('-a', '--access_key'):
283 aws_access_key_id = a
284 if o in ('-b', '--bucket'):
285 bucket_name = a
286 if o in ('-c', '--callback'):
287 num_cb = int(a)
288 cb = submit_cb
289 if o in ('-d', '--debug'):
290 debug = int(a)
291 if o in ('-g', '--grant'):
292 grant = a
293 if o in ('-i', '--ignore'):
294 ignore_dirs = a.split(',')
295 if o in ('-n', '--no_op'):
296 no_op = True
297 if o in ('-w', '--no_overwrite'):
298 no_overwrite = True
299 if o in ('-p', '--prefix'):
300 prefix = a
301 if prefix[-1] != os.sep:
302 prefix = prefix + os.sep
303 prefix = expand_path(prefix)
304 if o in ('-k', '--key_prefix'):
305 key_prefix = a
306 if o in ('-q', '--quiet'):
307 quiet = True
308 if o in ('-s', '--secret_key'):
309 aws_secret_access_key = a
310 if o in ('-r', '--reduced'):
311 reduced = True
312 if o == '--header':
313 (k, v) = a.split("=", 1)
314 headers[k] = v
315 if o == '--host':
316 host = a
317 if o == '--multipart':
318 if multipart_capable:
319 multipart_requested = True
320 else:
321 print("multipart upload requested but not capable")
322 sys.exit(4)
323 if o == '--region':
324 regions = boto.s3.regions()
325 for region_info in regions:
326 if region_info.name == a:
327 region = a
328 break
329 else:
330 raise ValueError('Invalid region %s specified' % a)
331
332 if len(args) < 1:
333 usage(2)
334
335 if not bucket_name:
336 print("bucket name is required!")
337 usage(3)
338
339 connect_args = {
340 'aws_access_key_id': aws_access_key_id,
341 'aws_secret_access_key': aws_secret_access_key
342 }
343
344 if host:
345 connect_args['host'] = host
346
347 c = boto.s3.connect_to_region(region or DEFAULT_REGION, **connect_args)
348 check_valid_region(c, region or DEFAULT_REGION)
349 c.debug = debug
350 b = c.get_bucket(bucket_name, validate=False)
351
352 # Attempt to determine location and warn if no --host or --region
353 # arguments were passed. Then try to automagically figure out
354 # what should have been passed and fix it.
355 if host is None and region is None:
356 try:
357 location = b.get_location()
358
359 # Classic region will be '', any other will have a name
360 if location:
361 print('Bucket exists in %s but no host or region given!' % location)
362
363 # Override for EU, which is really Ireland according to the docs
364 if location == 'EU':
365 location = 'eu-west-1'
366
367 print('Automatically setting region to %s' % location)
368
369 # Here we create a new connection, and then take the existing
370 # bucket and set it to use the new connection
371 c = boto.s3.connect_to_region(location, **connect_args)
372 c.debug = debug
373 b.connection = c
374 except Exception as e:
375 if debug > 0:
376 print(e)
377 print('Could not get bucket region info, skipping...')
378
379 existing_keys_to_check_against = []
380 files_to_check_for_upload = []
381
382 for path in args:
383 path = expand_path(path)
384 # upload a directory of files recursively
385 if os.path.isdir(path):
386 if no_overwrite:
387 if not quiet:
388 print('Getting list of existing keys to check against')
389 for key in b.list(get_key_name(path, prefix, key_prefix)):
390 existing_keys_to_check_against.append(key.name)
391 for root, dirs, files in os.walk(path):
392 for ignore in ignore_dirs:
393 if ignore in dirs:
394 dirs.remove(ignore)
395 for path in files:
396 if path.startswith("."):
397 continue
398 files_to_check_for_upload.append(os.path.join(root, path))
399
400 # upload a single file
401 elif os.path.isfile(path):
402 fullpath = os.path.abspath(path)
403 key_name = get_key_name(fullpath, prefix, key_prefix)
404 files_to_check_for_upload.append(fullpath)
405 existing_keys_to_check_against.append(key_name)
406
407 # we are trying to upload something unknown
408 else:
409 print("I don't know what %s is, so i can't upload it" % path)
410
411 for fullpath in files_to_check_for_upload:
412 key_name = get_key_name(fullpath, prefix, key_prefix)
413
414 if no_overwrite and key_name in existing_keys_to_check_against:
415 if b.get_key(key_name):
416 if not quiet:
417 print('Skipping %s as it exists in s3' % fullpath)
418 continue
419
420 if not quiet:
421 print('Copying %s to %s/%s' % (fullpath, bucket_name, key_name))
422
423 if not no_op:
424 # 0-byte files don't work and also don't need multipart upload
425 if os.stat(fullpath).st_size != 0 and multipart_capable and \
426 multipart_requested:
427 multipart_upload(bucket_name, aws_access_key_id,
428 aws_secret_access_key, fullpath, key_name,
429 reduced, debug, cb, num_cb,
430 grant or 'private', headers,
431 region=region or DEFAULT_REGION)
432 else:
433 singlepart_upload(b, key_name, fullpath, cb=cb, num_cb=num_cb,
434 policy=grant, reduced_redundancy=reduced,
435 headers=headers)
436
437 if __name__ == "__main__":
438 main()