awsum 0.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|