awsum 0.5
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.
- data/.autotest +1 -0
- data/.gitignore +5 -0
- data/.rspec +1 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +44 -0
- data/LICENSE +19 -0
- data/README.rdoc +42 -0
- data/Rakefile +75 -0
- data/awsum.gemspec +199 -0
- data/lib/awsum.rb +20 -0
- data/lib/awsum/ec2.rb +741 -0
- data/lib/awsum/ec2/address.rb +67 -0
- data/lib/awsum/ec2/availability_zone.rb +16 -0
- data/lib/awsum/ec2/image.rb +62 -0
- data/lib/awsum/ec2/instance.rb +57 -0
- data/lib/awsum/ec2/keypair.rb +16 -0
- data/lib/awsum/ec2/parsers/address_parser.rb +61 -0
- data/lib/awsum/ec2/parsers/availability_zone_parser.rb +57 -0
- data/lib/awsum/ec2/parsers/image_parser.rb +74 -0
- data/lib/awsum/ec2/parsers/instance_parser.rb +90 -0
- data/lib/awsum/ec2/parsers/keypair_parser.rb +64 -0
- data/lib/awsum/ec2/parsers/purchase_reserved_instances_offering_parser.rb +34 -0
- data/lib/awsum/ec2/parsers/region_parser.rb +56 -0
- data/lib/awsum/ec2/parsers/register_image_parser.rb +34 -0
- data/lib/awsum/ec2/parsers/reserved_instance_parser.rb +64 -0
- data/lib/awsum/ec2/parsers/reserved_instances_offering_parser.rb +63 -0
- data/lib/awsum/ec2/parsers/security_group_parser.rb +106 -0
- data/lib/awsum/ec2/parsers/snapshot_parser.rb +64 -0
- data/lib/awsum/ec2/parsers/volume_parser.rb +77 -0
- data/lib/awsum/ec2/region.rb +54 -0
- data/lib/awsum/ec2/reserved_instance.rb +24 -0
- data/lib/awsum/ec2/reserved_instances_offering.rb +20 -0
- data/lib/awsum/ec2/security_group.rb +74 -0
- data/lib/awsum/ec2/snapshot.rb +23 -0
- data/lib/awsum/ec2/volume.rb +65 -0
- data/lib/awsum/error.rb +53 -0
- data/lib/awsum/net_fix.rb +100 -0
- data/lib/awsum/parser.rb +18 -0
- data/lib/awsum/requestable.rb +216 -0
- data/lib/awsum/s3.rb +220 -0
- data/lib/awsum/s3/bucket.rb +28 -0
- data/lib/awsum/s3/headers.rb +24 -0
- data/lib/awsum/s3/object.rb +138 -0
- data/lib/awsum/s3/parsers/bucket_parser.rb +43 -0
- data/lib/awsum/support.rb +94 -0
- data/spec/fixtures/ec2/addresses.xml +10 -0
- data/spec/fixtures/ec2/allocate_address.xml +5 -0
- data/spec/fixtures/ec2/associate_address.xml +5 -0
- data/spec/fixtures/ec2/attach_volume.xml +9 -0
- data/spec/fixtures/ec2/authorize_ip_access.xml +5 -0
- data/spec/fixtures/ec2/authorize_owner_group_access.xml +5 -0
- data/spec/fixtures/ec2/authorize_owner_group_access_error.xml +2 -0
- data/spec/fixtures/ec2/availability_zones.xml +16 -0
- data/spec/fixtures/ec2/available_volume.xml +14 -0
- data/spec/fixtures/ec2/create_key_pair.xml +29 -0
- data/spec/fixtures/ec2/create_security_group.xml +5 -0
- data/spec/fixtures/ec2/create_snapshot.xml +9 -0
- data/spec/fixtures/ec2/create_volume.xml +10 -0
- data/spec/fixtures/ec2/delete_key_pair.xml +5 -0
- data/spec/fixtures/ec2/delete_security_group.xml +5 -0
- data/spec/fixtures/ec2/delete_snapshot.xml +5 -0
- data/spec/fixtures/ec2/delete_volume.xml +5 -0
- data/spec/fixtures/ec2/deregister_image.xml +5 -0
- data/spec/fixtures/ec2/detach_volume.xml +9 -0
- data/spec/fixtures/ec2/disassociate_address.xml +5 -0
- data/spec/fixtures/ec2/image.xml +15 -0
- data/spec/fixtures/ec2/images.xml +77 -0
- data/spec/fixtures/ec2/instance.xml +36 -0
- data/spec/fixtures/ec2/instances.xml +88 -0
- data/spec/fixtures/ec2/internal_error.xml +2 -0
- data/spec/fixtures/ec2/invalid_amiid_error.xml +2 -0
- data/spec/fixtures/ec2/invalid_request_error.xml +2 -0
- data/spec/fixtures/ec2/key_pairs.xml +10 -0
- data/spec/fixtures/ec2/purchase_reserved_instances_offering.xml +5 -0
- data/spec/fixtures/ec2/purchase_reserved_instances_offerings.xml +6 -0
- data/spec/fixtures/ec2/regions.xml +14 -0
- data/spec/fixtures/ec2/register_image.xml +5 -0
- data/spec/fixtures/ec2/release_address.xml +5 -0
- data/spec/fixtures/ec2/reserved_instances.xml +18 -0
- data/spec/fixtures/ec2/reserved_instances_offering.xml +15 -0
- data/spec/fixtures/ec2/reserved_instances_offerings.xml +276 -0
- data/spec/fixtures/ec2/revoke_ip_access.xml +5 -0
- data/spec/fixtures/ec2/revoke_owner_group_access.xml +5 -0
- data/spec/fixtures/ec2/run_instances.xml +30 -0
- data/spec/fixtures/ec2/security_groups.xml +159 -0
- data/spec/fixtures/ec2/snapshots.xml +13 -0
- data/spec/fixtures/ec2/terminate_instances.xml +17 -0
- data/spec/fixtures/ec2/unassociated_address.xml +10 -0
- data/spec/fixtures/ec2/volumes.xml +23 -0
- data/spec/fixtures/errors/invalid_parameter_value.xml +2 -0
- data/spec/fixtures/s3/buckets.xml +2 -0
- data/spec/fixtures/s3/copy_failure.xml +23 -0
- data/spec/fixtures/s3/invalid_request_signature.xml +5 -0
- data/spec/fixtures/s3/keys.xml +2 -0
- data/spec/lib/awsum/ec2/address_spec.rb +149 -0
- data/spec/lib/awsum/ec2/availability_zones_spec.rb +21 -0
- data/spec/lib/awsum/ec2/image_spec.rb +92 -0
- data/spec/lib/awsum/ec2/instance_spec.rb +125 -0
- data/spec/lib/awsum/ec2/keypair_spec.rb +55 -0
- data/spec/lib/awsum/ec2/parsers/address_parser_spec.rb +51 -0
- data/spec/lib/awsum/ec2/parsers/availability_zone_parser_spec.rb +28 -0
- data/spec/lib/awsum/ec2/parsers/image_parser_spec.rb +66 -0
- data/spec/lib/awsum/ec2/parsers/instance_parser_spec.rb +75 -0
- data/spec/lib/awsum/ec2/parsers/keypair_parser_spec.rb +74 -0
- data/spec/lib/awsum/ec2/parsers/purchase_reserved_instances_offering_parser_spec.rb +14 -0
- data/spec/lib/awsum/ec2/parsers/region_parser_spec.rb +27 -0
- data/spec/lib/awsum/ec2/parsers/register_image_parser_spec.rb +15 -0
- data/spec/lib/awsum/ec2/parsers/reserved_instance_parser_spec.rb +35 -0
- data/spec/lib/awsum/ec2/parsers/reserved_instances_offering_parser_spec.rb +32 -0
- data/spec/lib/awsum/ec2/parsers/security_group_parser_spec.rb +78 -0
- data/spec/lib/awsum/ec2/parsers/snapshot_parser_spec.rb +30 -0
- data/spec/lib/awsum/ec2/parsers/volume_parser_spec.rb +35 -0
- data/spec/lib/awsum/ec2/region_spec.rb +73 -0
- data/spec/lib/awsum/ec2/reserved_instance_spec.rb +61 -0
- data/spec/lib/awsum/ec2/reserved_instances_offering_spec.rb +33 -0
- data/spec/lib/awsum/ec2/security_group_spec.rb +179 -0
- data/spec/lib/awsum/ec2/snapshots_spec.rb +69 -0
- data/spec/lib/awsum/ec2/volume_spec.rb +107 -0
- data/spec/lib/awsum/ec2_spec.rb +6 -0
- data/spec/lib/awsum/error_spec.rb +31 -0
- data/spec/lib/awsum/requestable_spec.rb +126 -0
- data/spec/lib/awsum/s3/bucket_spec.rb +95 -0
- data/spec/lib/awsum/s3/object_spec.rb +128 -0
- data/spec/lib/awsum/s3/parsers/bucket_parser_spec.rb +41 -0
- data/spec/lib/awsum/s3/parsers/object_parser_spec.rb +41 -0
- data/spec/spec_helper.rb +16 -0
- metadata +240 -0
data/lib/awsum/parser.rb
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require 'rexml/document'
|
|
2
|
+
|
|
3
|
+
module Awsum
|
|
4
|
+
class Parser #:nodoc:
|
|
5
|
+
def parse(xml_text)
|
|
6
|
+
REXML::Document.parse_stream(xml_text, self)
|
|
7
|
+
result
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
#--
|
|
11
|
+
#Methods to be overridden by each Parser implementation
|
|
12
|
+
def result ; end
|
|
13
|
+
def tag_start(tag, attributes) ; end
|
|
14
|
+
def text(text) ; end
|
|
15
|
+
def tag_end(tag) ; end
|
|
16
|
+
def xmldecl(*args) ; end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
require 'base64'
|
|
2
|
+
require 'cgi'
|
|
3
|
+
require 'digest/md5'
|
|
4
|
+
require 'openssl'
|
|
5
|
+
require 'time'
|
|
6
|
+
require 'uri'
|
|
7
|
+
require 'awsum/error'
|
|
8
|
+
|
|
9
|
+
require 'net/https'
|
|
10
|
+
require 'awsum/net_fix'
|
|
11
|
+
|
|
12
|
+
module Awsum
|
|
13
|
+
module Requestable #:nodoc:
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
# Sends a request with query parameters
|
|
17
|
+
#
|
|
18
|
+
# Used for EC2 requests
|
|
19
|
+
def send_query_request(params)
|
|
20
|
+
standard_options = {
|
|
21
|
+
'AWSAccessKeyId' => @access_key,
|
|
22
|
+
'Version' => API_VERSION,
|
|
23
|
+
'Timestamp' => Time.now.utc.strftime('%Y-%m-%dT%H:%M:%S.000Z'),
|
|
24
|
+
'SignatureVersion' => SIGNATURE_VERSION,
|
|
25
|
+
'SignatureMethod' => 'HmacSHA256'
|
|
26
|
+
}
|
|
27
|
+
params = standard_options.merge(params)
|
|
28
|
+
|
|
29
|
+
#Put parameters into query string format
|
|
30
|
+
params_string = params.delete_if{|k,v| v.nil?}.sort{|a,b|
|
|
31
|
+
a[0].to_s <=> b[0].to_s
|
|
32
|
+
}.collect{|key, val|
|
|
33
|
+
"#{CGI::escape(key.to_s)}=#{CGI::escape(val.to_s)}"
|
|
34
|
+
}.join('&')
|
|
35
|
+
params_string.gsub!('+', '%20')
|
|
36
|
+
|
|
37
|
+
#Create request signature
|
|
38
|
+
signature_string = "GET\n#{host}\n/\n#{params_string}"
|
|
39
|
+
signature = sign(signature_string, 'sha256')
|
|
40
|
+
|
|
41
|
+
#Attach signature to query string
|
|
42
|
+
params_string << "&Signature=#{CGI::escape(signature)}"
|
|
43
|
+
|
|
44
|
+
url = "https://#{host}/?#{params_string}"
|
|
45
|
+
response = process_request('GET', url)
|
|
46
|
+
if response.is_a?(Net::HTTPSuccess)
|
|
47
|
+
response
|
|
48
|
+
else
|
|
49
|
+
raise Awsum::Error.new(response)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Sends a full request including headers and possible post data
|
|
54
|
+
#
|
|
55
|
+
# Used for S3 requests
|
|
56
|
+
def send_s3_request(method = 'GET', options = {}, &block)
|
|
57
|
+
bucket = options[:bucket] || ''
|
|
58
|
+
key = options[:key] || ''
|
|
59
|
+
parameters = options[:parameters] || {}
|
|
60
|
+
data = options[:data]
|
|
61
|
+
headers = {}
|
|
62
|
+
(options[:headers] || {}).each { |k,v| headers[k.downcase] = v }
|
|
63
|
+
|
|
64
|
+
#Add the host header
|
|
65
|
+
host_name = "#{bucket}#{bucket.blank? ? '' : '.'}#{host}"
|
|
66
|
+
headers['host'] = host_name
|
|
67
|
+
|
|
68
|
+
# Ensure required headers are in place
|
|
69
|
+
if !data.nil? && headers['content-md5'].nil?
|
|
70
|
+
if data.respond_to?(:read)
|
|
71
|
+
if data.respond_to?(:rewind)
|
|
72
|
+
digest = Digest::MD5.new
|
|
73
|
+
while chunk = data.read(1024 * 1024)
|
|
74
|
+
digest << chunk
|
|
75
|
+
end
|
|
76
|
+
data.rewind
|
|
77
|
+
|
|
78
|
+
headers['content-md5'] = Base64::encode64(digest.digest).gsub(/\n/, '')
|
|
79
|
+
end
|
|
80
|
+
else
|
|
81
|
+
headers['content-md5'] = Base64::encode64(Digest::MD5.digest(data)).gsub(/\n/, '')
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
if !data.nil? && headers['content-length'].nil?
|
|
85
|
+
headers['content-length'] = (data.respond_to?(:lstat) ? data.lstat.size : data.size).to_s
|
|
86
|
+
end
|
|
87
|
+
headers['date'] = Time.now.rfc822 if headers['date'].nil?
|
|
88
|
+
headers['content-type'] ||= ''
|
|
89
|
+
if !data.nil?
|
|
90
|
+
headers['expect'] = '100-continue'
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
signature_string = generate_rest_signature_string(method, bucket, key, parameters, headers)
|
|
94
|
+
puts "signature_string: \n#{signature_string}\n\n" if ENV['DEBUG']
|
|
95
|
+
|
|
96
|
+
signature = sign(signature_string)
|
|
97
|
+
|
|
98
|
+
headers['authorization'] = "AWS #{@access_key}:#{signature}"
|
|
99
|
+
|
|
100
|
+
url = "https://#{host_name}#{key[0..0] == '/' ? '' : '/'}#{key}#{parameters.size == 0 ? '' : "?#{parameters.collect{|k,v| v.nil? ? k.to_s : "#{CGI::escape(k.to_s)}=#{CGI::escape(v.to_s)}"}.join('&')}"}"
|
|
101
|
+
|
|
102
|
+
process_request(method, url, headers, data) do |response|
|
|
103
|
+
if response.is_a?(Net::HTTPSuccess)
|
|
104
|
+
if block_given?
|
|
105
|
+
block.call(response)
|
|
106
|
+
else
|
|
107
|
+
response
|
|
108
|
+
end
|
|
109
|
+
else
|
|
110
|
+
raise Awsum::Error.new(response)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def generate_rest_signature_string(method, bucket, key, parameters, headers)
|
|
116
|
+
canonicalized_amz_headers = headers.sort{|a,b| a[0].to_s.downcase <=> b[0].to_s.downcase }.collect{|k, v| "#{k.to_s.downcase}:#{(v.is_a?(Array) ? v.join(',') : v).gsub(/\n/, ' ')}" if k =~ /\Ax-amz-/i }.compact.join("\n")
|
|
117
|
+
canonicalized_resource = "#{bucket.blank? ? '' : '/'}#{bucket}#{key[0..0] == '/' ? '' : '/'}#{key}"
|
|
118
|
+
['acl', 'torrent', 'logging', 'location'].each do |sub_resource|
|
|
119
|
+
if parameters.has_key?(sub_resource)
|
|
120
|
+
canonicalized_resource << "?#{sub_resource}"
|
|
121
|
+
break
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
signature_string = "#{method}\n#{headers['content-md5']}\n#{headers['content-type']}\n#{headers['x-amz-date'].nil? ? headers['date'] : ''}\n#{canonicalized_amz_headers}#{canonicalized_amz_headers.blank? ? '' : "\n"}#{canonicalized_resource}"
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def generate_s3_signed_request_url(method, bucket, key, expires = nil)
|
|
129
|
+
signature_string = generate_rest_signature_string(method, bucket, key, {}, {'date' => expires})
|
|
130
|
+
signature = sign(signature_string)
|
|
131
|
+
"http://#{bucket}#{bucket.blank? ? '' : '.'}#{host}#{key[0..0] == '/' ? '' : '/'}#{key}?AWSAccessKeyId=#{@access_key}&Signature=#{CGI::escape(signature)}&Expires=#{expires}"
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def process_request(method, url, headers = {}, data = nil, &block)
|
|
135
|
+
#TODO: Allow secure/non-secure
|
|
136
|
+
uri = URI.parse(url)
|
|
137
|
+
uri.scheme = 'https'
|
|
138
|
+
uri.port = 443
|
|
139
|
+
|
|
140
|
+
#puts uri.to_s
|
|
141
|
+
|
|
142
|
+
Net::HTTP.version_1_1
|
|
143
|
+
con = Net::HTTP.new(uri.host, uri.port)
|
|
144
|
+
con.use_ssl = true
|
|
145
|
+
con.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
|
146
|
+
con.start do |http|
|
|
147
|
+
case method.upcase
|
|
148
|
+
when 'GET'
|
|
149
|
+
request = Net::HTTP::Get.new("#{uri.path}#{"?#{uri.query}" if uri.query}")
|
|
150
|
+
when 'POST'
|
|
151
|
+
request = Net::HTTP::Post.new("#{uri.path}#{"?#{uri.query}" if uri.query}")
|
|
152
|
+
when 'PUT'
|
|
153
|
+
request = Net::HTTP::Put.new("#{uri.path}#{"?#{uri.query}" if uri.query}")
|
|
154
|
+
when 'DELETE'
|
|
155
|
+
request = Net::HTTP::Delete.new("#{uri.path}#{"?#{uri.query}" if uri.query}")
|
|
156
|
+
when 'HEAD'
|
|
157
|
+
request = Net::HTTP::Head.new("#{uri.path}#{"?#{uri.query}" if uri.query}")
|
|
158
|
+
end
|
|
159
|
+
request.initialize_http_header(headers)
|
|
160
|
+
|
|
161
|
+
unless data.nil?
|
|
162
|
+
if data.respond_to?(:read)
|
|
163
|
+
request.body_stream = data
|
|
164
|
+
else
|
|
165
|
+
request.body = data
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
if block_given?
|
|
170
|
+
http.request(request) do |response|
|
|
171
|
+
handle_response response, &block
|
|
172
|
+
end
|
|
173
|
+
else
|
|
174
|
+
response = http.request(request)
|
|
175
|
+
handle_response response
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def handle_response(response, &block)
|
|
181
|
+
case response
|
|
182
|
+
when Net::HTTPSuccess
|
|
183
|
+
if block_given?
|
|
184
|
+
block.call(response)
|
|
185
|
+
else
|
|
186
|
+
response
|
|
187
|
+
end
|
|
188
|
+
when Net::HTTPMovedPermanently, Net::HTTPFound, Net::HTTPTemporaryRedirect
|
|
189
|
+
new_uri = URI.parse(response['location'])
|
|
190
|
+
uri.host = new_uri.host
|
|
191
|
+
uri.path = "#{new_uri.path}#{uri.path unless uri.path = '/'}"
|
|
192
|
+
process_request(method, uri.to_s, headers, data, &block)
|
|
193
|
+
else
|
|
194
|
+
response
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Converts an array of paramters into <param_name>.<num> format
|
|
199
|
+
def array_to_params(arr, param_name)
|
|
200
|
+
arr = [arr] unless arr.is_a?(Array)
|
|
201
|
+
params = {}
|
|
202
|
+
arr.each_with_index do |value,i|
|
|
203
|
+
params["#{param_name}.#{i+1}"] = value
|
|
204
|
+
end
|
|
205
|
+
params
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Sign a string with a digest, wrap in HMAC digest and base64 encode
|
|
209
|
+
#
|
|
210
|
+
# ===Returns
|
|
211
|
+
# base64 encoded string with newlines removed
|
|
212
|
+
def sign(string, digest = 'sha1')
|
|
213
|
+
Base64::encode64(OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new(digest), @secret_key, string)).gsub(/\n/, '')
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
end
|
data/lib/awsum/s3.rb
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
require 'awsum'
|
|
2
|
+
require 'awsum/s3/bucket'
|
|
3
|
+
require 'awsum/s3/object'
|
|
4
|
+
require 'awsum/s3/headers'
|
|
5
|
+
|
|
6
|
+
module Awsum
|
|
7
|
+
# Handles all interaction with Amazon S3
|
|
8
|
+
#
|
|
9
|
+
#--
|
|
10
|
+
# TODO: Change this to S3
|
|
11
|
+
# ==Getting Started
|
|
12
|
+
# Create an Awsum::Ec2 object and begin calling methods on it.
|
|
13
|
+
# require 'rubygems'
|
|
14
|
+
# require 'awsum'
|
|
15
|
+
# ec2 = Awsum::Ec2.new('your access id', 'your secret key')
|
|
16
|
+
# images = ec2.my_images
|
|
17
|
+
# ...
|
|
18
|
+
#
|
|
19
|
+
# All calls to EC2 can be done directly in this class, or through a more
|
|
20
|
+
# object oriented way through the various returned classes
|
|
21
|
+
#
|
|
22
|
+
# ==Examples
|
|
23
|
+
# ec2.image('ami-ABCDEF').run
|
|
24
|
+
#
|
|
25
|
+
# ec2.instance('i-123456789').volumes.each do |vol|
|
|
26
|
+
# vol.create_snapsot
|
|
27
|
+
# end
|
|
28
|
+
#
|
|
29
|
+
# ec2.regions.each do |region|
|
|
30
|
+
# region.use
|
|
31
|
+
# images.each do |img|
|
|
32
|
+
# puts "#{img.id} - #{region.name}"
|
|
33
|
+
# end
|
|
34
|
+
# end
|
|
35
|
+
# end
|
|
36
|
+
#
|
|
37
|
+
# ==Errors
|
|
38
|
+
# All methods will raise an Awsum::Error if an error is returned from Amazon
|
|
39
|
+
#
|
|
40
|
+
# ==Missing Methods
|
|
41
|
+
# If you need any of this functionality, please consider getting involved
|
|
42
|
+
# and help complete this library.
|
|
43
|
+
class S3
|
|
44
|
+
include Awsum::Requestable
|
|
45
|
+
|
|
46
|
+
# Create an new S3 instance
|
|
47
|
+
#
|
|
48
|
+
# The access_key and secret_key are both required to do any meaningful work.
|
|
49
|
+
#
|
|
50
|
+
# If you want to get these keys from environment variables, you can do that
|
|
51
|
+
# in your code as follows:
|
|
52
|
+
# s3 = Awsum::S3.new(ENV['AWS_ACCESS_KEY_ID'], ENV['AWS_SECRET_ACCESS_KEY'])
|
|
53
|
+
def initialize(access_key = nil, secret_key = nil)
|
|
54
|
+
@access_key = access_key
|
|
55
|
+
@secret_key = secret_key
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# List all the Bucket(s)
|
|
59
|
+
def buckets
|
|
60
|
+
response = send_s3_request
|
|
61
|
+
parser = Awsum::S3::BucketParser.new(self)
|
|
62
|
+
parser.parse(response.body)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def bucket(name)
|
|
66
|
+
Bucket.new(self, name)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Create a new Bucket
|
|
70
|
+
#
|
|
71
|
+
# ===Parameters
|
|
72
|
+
# * <tt>bucket_name</tt> - The name of the new bucket
|
|
73
|
+
# * <tt>location</tt> <i>(optional)</i> - Can be <tt>:default</tt>, <tt>:us</tt> or <tt>:eu</tt>
|
|
74
|
+
def create_bucket(bucket_name, location = :default)
|
|
75
|
+
raise ArgumentError.new('Bucket name cannot be in an ip address style') if bucket_name =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/
|
|
76
|
+
raise ArgumentError.new('Bucket name can only have lowercase letters, numbers, periods (.), underscores (_) and dashes (-)') unless bucket_name =~ /^[\w\d][-a-z\d._]+[a-z\d._]$/
|
|
77
|
+
raise ArgumentError.new('Bucket name cannot contain a dash (-) next to a period (.)') if bucket_name =~ /\.-|-\./
|
|
78
|
+
raise ArgumentError.new('Bucket name must be between 3 and 63 characters') if bucket_name.size < 3 || bucket_name.size > 63
|
|
79
|
+
|
|
80
|
+
data = nil
|
|
81
|
+
if location == :eu
|
|
82
|
+
data = '<CreateBucketConfiguration><LocationConstraint>EU</LocationConstraint></CreateBucketConfiguration>'
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
response = send_s3_request('PUT', :bucket => bucket_name, :data => data)
|
|
86
|
+
response.is_a?(Net::HTTPSuccess)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def delete_bucket(bucket_name)
|
|
90
|
+
response = send_s3_request('DELETE', :bucket => bucket_name)
|
|
91
|
+
response.is_a?(Net::HTTPSuccess)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# List the Key(s) of a Bucket
|
|
95
|
+
#
|
|
96
|
+
# ===Parameters
|
|
97
|
+
# * <tt>bucket_name</tt> - The name of the bucket to search for keys
|
|
98
|
+
# ====Options
|
|
99
|
+
# * <tt>:prefix</tt> - Limits the response to keys which begin with the indicated prefix. You can use prefixes to separate a bucket into different sets of keys in a way similar to how a file system uses folders.
|
|
100
|
+
# * <tt>:marker</tt> - Indicates where in the bucket to begin listing. The list will only include keys that occur lexicographically after marker. This is convenient for pagination: To get the next page of results use the last key of the current page as the marker.
|
|
101
|
+
# * <tt>:max_keys</tt> - The maximum number of keys you'd like to see in the response body. The server might return fewer than this many keys, but will not return more.
|
|
102
|
+
# * <tt>:delimeter</tt> - Causes keys that contain the same string between the prefix and the first occurrence of the delimiter to be rolled up into a single result element in the CommonPrefixes collection. These rolled-up keys are not returned elsewhere in the response.
|
|
103
|
+
def keys(bucket_name, options = {})
|
|
104
|
+
paramters = {}
|
|
105
|
+
paramters['prefix'] = options[:prefix] if options[:prefix]
|
|
106
|
+
paramters['marker'] = options[:marker] if options[:marker]
|
|
107
|
+
paramters['max_keys'] = options[:max_keys] if options[:max_keys]
|
|
108
|
+
paramters['prefix'] = options[:prefix] if options[:prefix]
|
|
109
|
+
|
|
110
|
+
response = send_s3_request('GET', :bucket => bucket_name, :paramters => paramters)
|
|
111
|
+
parser = Awsum::S3::ObjectParser.new(self)
|
|
112
|
+
parser.parse(response.body)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Create a new Object in the specified Bucket
|
|
116
|
+
#
|
|
117
|
+
# ===Parameters
|
|
118
|
+
# * <tt>bucket_name</tt> - The name of the Bucket in which to store the Key
|
|
119
|
+
# * <tt>key</tt> - The name/path of the Key to store
|
|
120
|
+
# * <tt>data</tt> - The data to be stored in this Object
|
|
121
|
+
# * <tt>headers</tt> - Standard HTTP headers to be sent along
|
|
122
|
+
# * <tt>meta_headers</tt> - Meta headers to be stored along with the key
|
|
123
|
+
# * <tt>acl</tt> - A canned access policy, can be one of <tt>:private</tt>, <tt>:public_read</tt>, <tt>:public_read_write</tt> or <tt>:authenticated_read</tt>
|
|
124
|
+
def create_object(bucket_name, key, data, headers = {}, meta_headers = {}, acl = :private)
|
|
125
|
+
headers = headers.dup
|
|
126
|
+
meta_headers.each do |k,v|
|
|
127
|
+
headers[k =~ /^x-amz-meta-/i ? k : "x-amz-meta-#{k}"] = v
|
|
128
|
+
end
|
|
129
|
+
headers['x-amz-acl'] = acl.to_s.gsub(/_/, '-')
|
|
130
|
+
|
|
131
|
+
response = send_s3_request('PUT', :bucket => bucket_name, :key => key, :headers => headers, :data => data)
|
|
132
|
+
response.is_a?(Net::HTTPSuccess)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Retrieve the headers for this Object
|
|
136
|
+
#
|
|
137
|
+
# All header methods map directly to the Net::HTTPHeader module
|
|
138
|
+
def object_headers(bucket_name, key)
|
|
139
|
+
response = send_s3_request('HEAD', :bucket => bucket_name, :key => key)
|
|
140
|
+
Headers.new(response)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Retrieve the data stored for the specified Object
|
|
144
|
+
#
|
|
145
|
+
# You can get the data as a single call or add a block to retrieve the data in chunks
|
|
146
|
+
# ==Examples
|
|
147
|
+
# data = s3.object_data('test-bucket', 'key')
|
|
148
|
+
#
|
|
149
|
+
# or
|
|
150
|
+
#
|
|
151
|
+
# s3.object_data('test-bucket', 'key') do |chunk|
|
|
152
|
+
# # handle chunk
|
|
153
|
+
# puts chunk
|
|
154
|
+
# end
|
|
155
|
+
def object_data(bucket_name, key, &block)
|
|
156
|
+
send_s3_request('GET', :bucket => bucket_name, :key => key) do |response|
|
|
157
|
+
if block_given?
|
|
158
|
+
response.read_body &block
|
|
159
|
+
return true
|
|
160
|
+
else
|
|
161
|
+
return response.body
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Deletes an Object from a Bucket
|
|
167
|
+
def delete_object(bucket_name, key)
|
|
168
|
+
response = send_s3_request('DELETE', :bucket => bucket_name, :key => key)
|
|
169
|
+
response.is_a?(Net::HTTPSuccess)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Copy the contents of an Object to another key and/or bucket
|
|
173
|
+
#
|
|
174
|
+
# ===Parameters
|
|
175
|
+
# * <tt>source_bucket_name</tt> - The name of the Bucket from which to copy
|
|
176
|
+
# * <tt>source_key</tt> - The name of the Key from which to copy
|
|
177
|
+
# * <tt>destination_bucket_name</tt> - The name of the Bucket to which to copy (Can be nil if copying within the same bucket, or updating header data of existing Key)
|
|
178
|
+
# * <tt>destination_key</tt> - The name of the Key to which to copy (Can be nil if copying to a new bucket with same key, or updating header data of existing Key)
|
|
179
|
+
# * <tt>headers</tt> - If not nil, the headers are replaced with this information
|
|
180
|
+
# * <tt>meta_headers</tt> - If not nil, the meta headers are replaced with this information
|
|
181
|
+
#
|
|
182
|
+
#--
|
|
183
|
+
# TODO: Need to handle copy-if-... headers
|
|
184
|
+
def copy_object(source_bucket_name, source_key, destination_bucket_name = nil, destination_key= nil, headers = nil, meta_headers = nil)
|
|
185
|
+
raise ArgumentError.new('You must include one of destination_bucket_name, destination_key or headers to be replaced') if destination_bucket_name.nil? && destination_key.nil? && headers.nil? && meta_headers.nil?
|
|
186
|
+
|
|
187
|
+
headers = {
|
|
188
|
+
'x-amz-copy-source' => "/#{source_bucket_name}/#{source_key}",
|
|
189
|
+
'x-amz-metadata-directive' => (((destination_bucket_name.nil? && destination_key.nil?) || !(headers.nil? || meta_headers.nil?)) ? 'REPLACE' : 'COPY')
|
|
190
|
+
}.merge(headers||{})
|
|
191
|
+
meta_headers.each do |k,v|
|
|
192
|
+
headers[k =~ /^x-amz-meta-/i ? k : "x-amz-meta-#{k}"] = v
|
|
193
|
+
end unless meta_headers.nil?
|
|
194
|
+
|
|
195
|
+
destination_bucket_name ||= source_bucket_name
|
|
196
|
+
destination_key ||= source_key
|
|
197
|
+
|
|
198
|
+
response = send_s3_request('PUT', :bucket => destination_bucket_name, :key => destination_key, :headers => headers, :data => nil)
|
|
199
|
+
if response.is_a?(Net::HTTPSuccess)
|
|
200
|
+
#Check for delayed error (See http://docs.amazonwebservices.com/AmazonS3/2006-03-01/RESTObjectCOPY.html#RESTObjectCOPY_Response)
|
|
201
|
+
response_body = response.body
|
|
202
|
+
if response_body =~ /<Error>/i
|
|
203
|
+
raise Awsum::Error.new(response)
|
|
204
|
+
else
|
|
205
|
+
true
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
#private
|
|
211
|
+
#The host to make all requests against
|
|
212
|
+
def host
|
|
213
|
+
@host ||= 's3.amazonaws.com'
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def host=(host)
|
|
217
|
+
@host = host
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
end
|