mgreenly-s3sync 1.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,143 @@
1
+ # This software code is made available "AS IS" without warranties of any
2
+ # kind. You may copy, display, modify and redistribute the software
3
+ # code either by itself or as incorporated into your code; provided that
4
+ # you do not remove any proprietary notices. Your use of this software
5
+ # code is at your own risk and you waive any claim against Amazon
6
+ # Digital Services, Inc. or its affiliates with respect to your use of
7
+ # this software code. (c) 2006 Amazon Digital Services, Inc. or its
8
+ # affiliates.
9
+ #
10
+ # This software code is made available "AS IS" without warranties of any
11
+ # kind. You may copy, display, modify and redistribute the software
12
+ # code either by itself or as incorporated into your code; provided that
13
+ # you do not remove any proprietary notices. Your use of this software
14
+ # code is at your own risk and you waive any claim against the author
15
+ # with respect to your use of this software code.
16
+ # (c) 2007 s3sync.net
17
+ #
18
+ require 'S3'
19
+ require 'HTTPStreaming'
20
+
21
+ # The purpose of this file is to overlay the S3 library from AWS
22
+ # to add some functionality
23
+ # (without changing the file itself or requiring a specific version)
24
+ # It still isn't perfectly robust, i.e. if radical changes are made
25
+ # to the underlying lib this stuff will need updating.
26
+
27
+ module S3
28
+ class AWSAuthConnection
29
+
30
+ def make_http(bucket='', host='', proxy_host=nil, proxy_port=nil, proxy_user=nil, proxy_pass=nil)
31
+
32
+ # build the domain based on the calling format
33
+ server = ''
34
+ if host != ''
35
+ server = host
36
+ elsif bucket.empty?
37
+ # for a bucketless request (i.e. list all buckets)
38
+ # revert to regular domain case since this operation
39
+ # does not make sense for vanity domains
40
+ server = @server
41
+ elsif @calling_format == CallingFormat::SUBDOMAIN
42
+ server = "#{bucket}.#{@server}"
43
+ elsif @calling_format == CallingFormat::VANITY
44
+ server = bucket
45
+ else
46
+ server = @server
47
+ end
48
+ # automatically does the right thing when no proxy
49
+ http = Net::HTTP::Proxy(proxy_host, proxy_port, proxy_user, proxy_pass).new(server, @port)
50
+ #http = Net::HTTP.new(server, @port)
51
+ http.use_ssl = @is_secure
52
+ http.verify_mode=@verify_mode
53
+ http.ca_file=@ca_file
54
+ http.ca_path=@ca_path
55
+ http.start
56
+ return http
57
+ end
58
+
59
+ # add support for streaming the response body to an IO stream
60
+ alias __make_request__ make_request
61
+ def make_request(method, bucket='', key='', path_args={}, headers={}, data='', metadata={}, streamOut=nil)
62
+ # build the path based on the calling format
63
+ path = ''
64
+ if (not bucket.empty?) and (@calling_format == CallingFormat::REGULAR)
65
+ path << "/#{bucket}"
66
+ end
67
+ # add the slash after the bucket regardless
68
+ # the key will be appended if it is non-empty
69
+ path << "/#{key}"
70
+
71
+ # build the path_argument string
72
+ # add the ? in all cases since
73
+ # signature and credentials follow path args
74
+ path << '?'
75
+ path << S3.path_args_hash_to_string(path_args)
76
+
77
+ req = method_to_request_class(method).new("#{path}")
78
+
79
+ set_headers(req, headers)
80
+ set_headers(req, metadata, METADATA_PREFIX)
81
+ set_headers(req, {'Connection' => 'keep-alive', 'Keep-Alive' => '300'})
82
+
83
+ set_aws_auth_header(req, @aws_access_key_id, @aws_secret_access_key, bucket, key, path_args)
84
+
85
+ http = $S3syncHttp
86
+
87
+ if req.request_body_permitted?
88
+ return http.request(req, data, streamOut)
89
+ else
90
+ return http.request(req, nil, streamOut)
91
+ end
92
+ end
93
+
94
+ # a "get" operation that sends the body to an IO stream
95
+ def get_stream(bucket, key, headers={}, streamOut=nil)
96
+ return GetResponse.new(make_request('GET', bucket, CGI::escape(key), {}, headers, '', {}, streamOut))
97
+ end
98
+
99
+ # a "get" operation that sends the body to an IO stream
100
+ def get_query_stream(bucket, key, path_args={}, headers={}, streamOut=nil)
101
+ return GetResponse.new(make_request('GET', bucket, CGI::escape(key), path_args, headers, '', {}, streamOut))
102
+ end
103
+
104
+ def head(bucket, key=nil, headers={})
105
+ return GetResponse.new(make_request('HEAD', bucket, CGI::escape(key), {}, headers, '', {}))
106
+ end
107
+ undef create_bucket
108
+ def create_bucket(bucket, object)
109
+ object = S3Object.new(object) if not object.instance_of? S3Object
110
+ return Response.new(
111
+ make_request('PUT', bucket, '', {}, {}, object.data, object.metadata)
112
+ )
113
+ end
114
+ # no, because internally the library does not support the header,wait,body paradigm, so this is useless
115
+ #alias __put__ put
116
+ #def put(bucket, key, object, headers={})
117
+ # headers['Expect'] = "100-continue"
118
+ # __put__(bucket, key, object, headers)
119
+ #end
120
+
121
+
122
+ # allow ssl validation
123
+ attr_accessor :verify_mode
124
+ attr_accessor :ca_path
125
+ attr_accessor :ca_file
126
+
127
+ end
128
+ module CallingFormat
129
+ def CallingFormat.string_to_format(s)
130
+ case s
131
+ when 'REGULAR'
132
+ return CallingFormat::REGULAR
133
+ when 'SUBDOMAIN'
134
+ return CallingFormat::SUBDOMAIN
135
+ when 'VANITY'
136
+ return CallingFormat::VANITY
137
+ else
138
+ raise "Unsupported calling format #{s}"
139
+ end
140
+ end
141
+ end
142
+
143
+ end
@@ -0,0 +1,50 @@
1
+ # This software code is made available "AS IS" without warranties of any
2
+ # kind. You may copy, display, modify and redistribute the software
3
+ # code either by itself or as incorporated into your code; provided that
4
+ # you do not remove any proprietary notices. Your use of this software
5
+ # code is at your own risk and you waive any claim against the author
6
+ # with respect to your use of this software code.
7
+ # (c) 2007 s3sync.net
8
+ #
9
+
10
+ # The purpose of this file is to overlay the cgi class
11
+ # to add some functionality
12
+ # (without changing the file itself or requiring a specific version)
13
+ # It still isn't perfectly robust, i.e. if radical changes are made
14
+ # to the underlying lib this stuff will need updating.
15
+
16
+ require 'cgi'
17
+ require 'iconv' # for UTF-8 conversion
18
+
19
+ # thanks to http://www.redhillconsulting.com.au/blogs/simon/archives/000326.html
20
+ module S3ExtendCGI
21
+ def self.included(base)
22
+ base.extend(ClassMethods)
23
+ base.class_eval do
24
+ class << self
25
+ alias_method :S3Extend_escape_orig, :escape unless method_defined?(:S3Extend_escape_orig)
26
+ alias_method :escape, :S3Extend_escape
27
+ end
28
+ end
29
+ end
30
+ module ClassMethods
31
+ @@exemptSlashesInEscape = false
32
+ attr_writer :exemptSlashesInEscape
33
+ @@usePercent20InEscape = false
34
+ attr_writer :usePercent20InEscape
35
+ @@nativeCharacterEncoding = "ISO-8859-1"
36
+ attr_writer :nativeCharacterEncoding
37
+ @@useUTF8InEscape = false
38
+ attr_writer :useUTF8InEscape
39
+
40
+ def S3Extend_escape(string)
41
+ result = string
42
+ result = Iconv.iconv("UTF-8", @nativeCharacterEncoding, string).join if @useUTF8InEscape
43
+ result = S3Extend_escape_orig(result)
44
+ result.gsub!(/%2f/i, "/") if @exemptSlashesInEscape
45
+ result.gsub!("+", "%20") if @usePercent20InEscape
46
+ result
47
+ end
48
+ end
49
+ end
50
+ CGI.send(:include, S3ExtendCGI)
@@ -0,0 +1,245 @@
1
+ #!/usr/bin/env ruby
2
+ # This software code is made available "AS IS" without warranties of any
3
+ # kind. You may copy, display, modify and redistribute the software
4
+ # code either by itself or as incorporated into your code; provided that
5
+ # you do not remove any proprietary notices. Your use of this software
6
+ # code is at your own risk and you waive any claim against the author
7
+ # with respect to your use of this software code.
8
+ # (c) 2007 s3sync.net
9
+ #
10
+
11
+ module S3sync
12
+
13
+ # always look "here" for include files (thanks aktxyz)
14
+ $LOAD_PATH << File.expand_path(File.dirname(__FILE__))
15
+
16
+ require 's3try'
17
+
18
+ $S3CMD_VERSION = '1.2.4'
19
+
20
+ require 'getoptlong'
21
+
22
+ # after other mods, so we don't overwrite yaml vals with defaults
23
+ require 's3config'
24
+ include S3Config
25
+
26
+ def S3sync.s3cmdMain
27
+ # ---------- OPTIONS PROCESSING ---------- #
28
+
29
+ $S3syncOptions = Hash.new
30
+ optionsParser = GetoptLong.new(
31
+ [ '--help', '-h', GetoptLong::NO_ARGUMENT ],
32
+ [ '--ssl', '-s', GetoptLong::NO_ARGUMENT ],
33
+ [ '--verbose', '-v', GetoptLong::NO_ARGUMENT ],
34
+ [ '--dryrun', '-n', GetoptLong::NO_ARGUMENT ],
35
+ [ '--debug', '-d', GetoptLong::NO_ARGUMENT ],
36
+ [ '--progress', GetoptLong::NO_ARGUMENT ],
37
+ [ '--expires-in', GetoptLong::REQUIRED_ARGUMENT ]
38
+ )
39
+
40
+ def S3sync.s3cmdUsage(message = nil)
41
+ $stderr.puts message if message
42
+ name = $0.split('/').last
43
+ $stderr.puts <<"ENDUSAGE"
44
+ #{name} [options] <command> [arg(s)]\t\tversion #{$S3CMD_VERSION}
45
+ --help -h --verbose -v --dryrun -n
46
+ --ssl -s --debug -d --progress
47
+ --expires-in=( <# of seconds> | [#d|#h|#m|#s] )
48
+
49
+ Commands:
50
+ #{name} listbuckets [headers]
51
+ #{name} createbucket <bucket> [constraint (i.e. EU)]
52
+ #{name} deletebucket <bucket> [headers]
53
+ #{name} list <bucket>[:prefix] [max/page] [delimiter] [headers]
54
+ #{name} location <bucket> [headers]
55
+ #{name} delete <bucket>:key [headers]
56
+ #{name} deleteall <bucket>[:prefix] [headers]
57
+ #{name} get|put <bucket>:key <file> [headers]
58
+ ENDUSAGE
59
+ exit
60
+ end #usage
61
+
62
+ begin
63
+ optionsParser.each {|opt, arg| $S3syncOptions[opt] = (arg || true)}
64
+ rescue StandardError
65
+ s3cmdUsage # the parser already printed an error message
66
+ end
67
+ s3cmdUsage if $S3syncOptions['--help']
68
+ $S3syncOptions['--verbose'] = true if $S3syncOptions['--dryrun'] or $S3syncOptions['--debug'] or $S3syncOptions['--progress']
69
+ $S3syncOptions['--ssl'] = true if $S3syncOptions['--ssl'] # change from "" to true to appease s3 port chooser
70
+
71
+ if $S3syncOptions['--expires-in'] =~ /d|h|m|s/
72
+ e = $S3syncOptions['--expires-in']
73
+ days = (e =~ /(\d+)d/)? (/(\d+)d/.match(e))[1].to_i : 0
74
+ hours = (e =~ /(\d+)h/)? (/(\d+)h/.match(e))[1].to_i : 0
75
+ minutes = (e =~ /(\d+)m/)? (/(\d+)m/.match(e))[1].to_i : 0
76
+ seconds = (e =~ /(\d+)s/)? (/(\d+)s/.match(e))[1].to_i : 0
77
+ $S3syncOptions['--expires-in'] = seconds + 60 * ( minutes + 60 * ( hours + 24 * ( days ) ) )
78
+ end
79
+
80
+ # ---------- CONNECT ---------- #
81
+ S3sync::s3trySetup
82
+ # ---------- COMMAND PROCESSING ---------- #
83
+
84
+ command, path, file = ARGV
85
+
86
+ s3cmdUsage("You didn't set up your environment variables; see README.txt") if not($AWS_ACCESS_KEY_ID and $AWS_SECRET_ACCESS_KEY)
87
+ s3cmdUsage("Need a command (etc)") if not command
88
+
89
+ path = '' unless path
90
+ path = path.dup # modifiable
91
+ path += ':' unless path.match(':')
92
+ bucket = (/^(.*?):/.match(path))[1]
93
+ path.replace((/:(.*)$/.match(path))[1])
94
+
95
+ case command
96
+ when "delete"
97
+ s3cmdUsage("Need a bucket") if bucket == ''
98
+ s3cmdUsage("Need a key") if path == ''
99
+ headers = hashPairs(ARGV[2...ARGV.length])
100
+ $stderr.puts "delete #{bucket}:#{path} #{headers.inspect if headers}" if $S3syncOptions['--verbose']
101
+ S3try(:delete, bucket, path) unless $S3syncOptions['--dryrun']
102
+ when "deleteall"
103
+ s3cmdUsage("Need a bucket") if bucket == ''
104
+ headers = hashPairs(ARGV[2...ARGV.length])
105
+ $stderr.puts "delete ALL entries in #{bucket}:#{path} #{headers.inspect if headers}" if $S3syncOptions['--verbose']
106
+ more = true
107
+ marker = nil
108
+ while more do
109
+ res = s3cmdList(bucket, path, nil, nil, marker)
110
+ res.entries.each do |item|
111
+ # the s3 commands (with my modified UTF-8 conversion) expect native char encoding input
112
+ key = Iconv.iconv($S3SYNC_NATIVE_CHARSET, "UTF-8", item.key).join
113
+ $stderr.puts "delete #{bucket}:#{key} #{headers.inspect if headers}" if $S3syncOptions['--verbose']
114
+ S3try(:delete, bucket, key) unless $S3syncOptions['--dryrun']
115
+ end
116
+ more = res.properties.is_truncated
117
+ marker = (res.properties.next_marker)? res.properties.next_marker : ((res.entries.length > 0) ? res.entries.last.key : nil)
118
+ # get this into local charset; when we pass it to s3 that is what's expected
119
+ marker = Iconv.iconv($S3SYNC_NATIVE_CHARSET, "UTF-8", marker).join if marker
120
+ end
121
+ when "list"
122
+ s3cmdUsage("Need a bucket") if bucket == ''
123
+ max, delim = ARGV[2..3]
124
+ headers = hashPairs(ARGV[4...ARGV.length])
125
+ $stderr.puts "list #{bucket}:#{path} #{max} #{delim} #{headers.inspect if headers}" if $S3syncOptions['--verbose']
126
+ puts "--------------------"
127
+
128
+ more = true
129
+ marker = nil
130
+ while more do
131
+ res = s3cmdList(bucket, path, max, delim, marker, headers)
132
+ if delim
133
+ res.common_prefix_entries.each do |item|
134
+
135
+ puts "dir: " + Iconv.iconv($S3SYNC_NATIVE_CHARSET, "UTF-8", item.prefix).join
136
+ end
137
+ puts "--------------------"
138
+ end
139
+ res.entries.each do |item|
140
+ puts Iconv.iconv($S3SYNC_NATIVE_CHARSET, "UTF-8", item.key).join
141
+ end
142
+ if res.properties.is_truncated
143
+ printf "More? Y/n: "
144
+ more = (STDIN.gets.match('^[Yy]?$'))
145
+ marker = (res.properties.next_marker)? res.properties.next_marker : ((res.entries.length > 0) ? res.entries.last.key : nil)
146
+ # get this into local charset; when we pass it to s3 that is what's expected
147
+ marker = Iconv.iconv($S3SYNC_NATIVE_CHARSET, "UTF-8", marker).join if marker
148
+
149
+ else
150
+ more = false
151
+ end
152
+ end # more
153
+ when "listbuckets"
154
+ headers = hashPairs(ARGV[1...ARGV.length])
155
+ $stderr.puts "list all buckets #{headers.inspect if headers}" if $S3syncOptions['--verbose']
156
+ if $S3syncOptions['--expires-in']
157
+ $stdout.puts S3url(:list_all_my_buckets, headers)
158
+ else
159
+ res = S3try(:list_all_my_buckets, headers)
160
+ res.entries.each do |item|
161
+ puts item.name
162
+ end
163
+ end
164
+ when "createbucket"
165
+ s3cmdUsage("Need a bucket") if bucket == ''
166
+ lc = ''
167
+ if(ARGV.length > 2)
168
+ lc = '<CreateBucketConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01"><LocationConstraint>' + ARGV[2] + '</LocationConstraint></CreateBucketConfiguration>'
169
+ end
170
+ $stderr.puts "create bucket #{bucket} #{lc}" if $S3syncOptions['--verbose']
171
+ S3try(:create_bucket, bucket, lc) unless $S3syncOptions['--dryrun']
172
+ when "deletebucket"
173
+ s3cmdUsage("Need a bucket") if bucket == ''
174
+ headers = hashPairs(ARGV[2...ARGV.length])
175
+ $stderr.puts "delete bucket #{bucket} #{headers.inspect if headers}" if $S3syncOptions['--verbose']
176
+ S3try(:delete_bucket, bucket, headers) unless $S3syncOptions['--dryrun']
177
+ when "location"
178
+ s3cmdUsage("Need a bucket") if bucket == ''
179
+ headers = hashPairs(ARGV[2...ARGV.length])
180
+ query = Hash.new
181
+ query['location'] = 'location'
182
+ $stderr.puts "location request bucket #{bucket} #{query.inspect} #{headers.inspect if headers}" if $S3syncOptions['--verbose']
183
+ S3try(:get_query_stream, bucket, '', query, headers, $stdout) unless $S3syncOptions['--dryrun']
184
+ when "get"
185
+ s3cmdUsage("Need a bucket") if bucket == ''
186
+ s3cmdUsage("Need a key") if path == ''
187
+ s3cmdUsage("Need a file") if file == ''
188
+ headers = hashPairs(ARGV[3...ARGV.length])
189
+ $stderr.puts "get from key #{bucket}:#{path} into #{file} #{headers.inspect if headers}" if $S3syncOptions['--verbose']
190
+ unless $S3syncOptions['--dryrun']
191
+ if $S3syncOptions['--expires-in']
192
+ $stdout.puts S3url(:get, bucket, path, headers)
193
+ else
194
+ outStream = File.open(file, 'wb')
195
+ outStream = ProgressStream.new(outStream) if $S3syncOptions['--progress']
196
+ S3try(:get_stream, bucket, path, headers, outStream)
197
+ outStream.close
198
+ end
199
+ end
200
+ when "put"
201
+ s3cmdUsage("Need a bucket") if bucket == ''
202
+ s3cmdUsage("Need a key") if path == ''
203
+ s3cmdUsage("Need a file") if file == ''
204
+ headers = hashPairs(ARGV[3...ARGV.length])
205
+ stream = File.open(file, 'rb')
206
+ stream = ProgressStream.new(stream, File.stat(file).size) if $S3syncOptions['--progress']
207
+ s3o = S3::S3Object.new(stream, {}) # support meta later?
208
+ headers['Content-Length'] = FileTest.size(file).to_s
209
+ $stderr.puts "put to key #{bucket}:#{path} from #{file} #{headers.inspect if headers}" if $S3syncOptions['--verbose']
210
+ S3try(:put, bucket, path, s3o, headers) unless $S3syncOptions['--dryrun']
211
+ stream.close
212
+ else
213
+ s3cmdUsage
214
+ end
215
+
216
+ end #main
217
+ def S3sync.s3cmdList(bucket, path, max=nil, delim=nil, marker=nil, headers={})
218
+ debug(max)
219
+ options = Hash.new
220
+ options['prefix'] = path # start at the right depth
221
+ options['max-keys'] = max ? max.to_s : 100
222
+ options['delimiter'] = delim if delim
223
+ options['marker'] = marker if marker
224
+ S3try(:list_bucket, bucket, options, headers)
225
+ end
226
+
227
+ # turn an array into a hash of pairs
228
+ def S3sync.hashPairs(ar)
229
+ ret = Hash.new
230
+ ar.each do |item|
231
+ name = (/^(.*?):/.match(item))[1]
232
+ item = (/^.*?:(.*)$/.match(item))[1]
233
+ ret[name] = item
234
+ end if ar
235
+ ret
236
+ end
237
+ end #module
238
+
239
+
240
+
241
+ def debug(str)
242
+ $stderr.puts str if $S3syncOptions['--debug']
243
+ end
244
+
245
+ S3sync::s3cmdMain #go!
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/ruby
2
+ # This software code is made available "AS IS" without warranties of any
3
+ # kind. You may copy, display, modify and redistribute the software
4
+ # code either by itself or as incorporated into your code; provided that
5
+ # you do not remove any proprietary notices. Your use of this software
6
+ # code is at your own risk and you waive any claim against the author
7
+ # with respect to your use of this software code.
8
+ # (c) 2007 alastair brunton
9
+ #
10
+ # modified to search out the yaml in several places, thanks wkharold.
11
+ require 'yaml'
12
+
13
+ module S3Config
14
+
15
+ confpath = ["#{ENV['S3CONF']}", "#{ENV['HOME']}/.s3conf", "/etc/s3conf"]
16
+
17
+ confpath.each do |path|
18
+ if File.exists?(path) and File.directory?(path) and File.exists?("#{path}/s3config.yml")
19
+ config = YAML.load_file("#{path}/s3config.yml")
20
+ config.each_pair do |key, value|
21
+ eval("$#{key.upcase} = '#{value}'")
22
+ end
23
+ break
24
+ end
25
+ end
26
+
27
+ end