staugaard-cloudmaster 0.1.1
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/VERSION.yml +4 -0
- data/bin/cloudmaster +45 -0
- data/lib/AWS/AWS.rb +3 -0
- data/lib/AWS/EC2.rb +14 -0
- data/lib/AWS/S3.rb +14 -0
- data/lib/AWS/SQS.rb +14 -0
- data/lib/AWS/SimpleDB.rb +14 -0
- data/lib/MockAWS/EC2.rb +119 -0
- data/lib/MockAWS/S3.rb +39 -0
- data/lib/MockAWS/SQS.rb +82 -0
- data/lib/MockAWS/SimpleDB.rb +46 -0
- data/lib/MockAWS/clock.rb +67 -0
- data/lib/OriginalAWS/AWS.rb +475 -0
- data/lib/OriginalAWS/EC2.rb +783 -0
- data/lib/OriginalAWS/S3.rb +559 -0
- data/lib/OriginalAWS/SQS.rb +159 -0
- data/lib/OriginalAWS/SimpleDB.rb +460 -0
- data/lib/RetryAWS/EC2.rb +88 -0
- data/lib/RetryAWS/S3.rb +77 -0
- data/lib/RetryAWS/SQS.rb +109 -0
- data/lib/RetryAWS/SimpleDB.rb +118 -0
- data/lib/SafeAWS/EC2.rb +63 -0
- data/lib/SafeAWS/S3.rb +56 -0
- data/lib/SafeAWS/SQS.rb +75 -0
- data/lib/SafeAWS/SimpleDB.rb +88 -0
- data/lib/aws_context.rb +165 -0
- data/lib/basic_configuration.rb +120 -0
- data/lib/clock.rb +10 -0
- data/lib/factory.rb +14 -0
- data/lib/file_logger.rb +36 -0
- data/lib/inifile.rb +148 -0
- data/lib/instance_logger.rb +25 -0
- data/lib/logger_factory.rb +38 -0
- data/lib/periodic.rb +29 -0
- data/lib/string_logger.rb +29 -0
- data/lib/sys_logger.rb +40 -0
- data/lib/user_data.rb +30 -0
- data/test/aws-config.ini +9 -0
- data/test/cloudmaster-tests.rb +329 -0
- data/test/configuration-test.rb +62 -0
- data/test/daytime-policy-tests.rb +47 -0
- data/test/enumerator-test.rb +47 -0
- data/test/fixed-policy-tests.rb +50 -0
- data/test/instance-pool-test.rb +359 -0
- data/test/instance-test.rb +98 -0
- data/test/job-policy-test.rb +95 -0
- data/test/manual-policy-tests.rb +63 -0
- data/test/named-queue-test.rb +90 -0
- data/test/resource-policy-tests.rb +126 -0
- data/test/suite +17 -0
- data/test/test-config.ini +47 -0
- metadata +111 -0
@@ -0,0 +1,475 @@
|
|
1
|
+
# Sample Ruby code for the O'Reilly book "Programming Amazon Web
|
2
|
+
# Services" by James Murty.
|
3
|
+
#
|
4
|
+
# This code was written for Ruby version 1.8.6 or greater.
|
5
|
+
#
|
6
|
+
# The AWS module includes HTTP messaging and utility methods that handle
|
7
|
+
# communication with Amazon Web Services' REST or Query APIs. Service
|
8
|
+
# client implementations are built on top of this module.
|
9
|
+
|
10
|
+
require 'openssl'
|
11
|
+
require 'digest/sha1'
|
12
|
+
require 'base64'
|
13
|
+
require 'cgi'
|
14
|
+
require 'net/https'
|
15
|
+
require 'time'
|
16
|
+
require 'uri'
|
17
|
+
require 'rexml/document'
|
18
|
+
|
19
|
+
$KCODE = 'u' # Enable Unicode (international character) support
|
20
|
+
|
21
|
+
module AWS
|
22
|
+
# Your Amazon Web Services Access Key credential.
|
23
|
+
attr_accessor :aws_access_key
|
24
|
+
|
25
|
+
# Your Amazon Web Services Secret Key credential.
|
26
|
+
attr_accessor :aws_secret_key
|
27
|
+
|
28
|
+
# Use only the Secure HTTP protocol (HTTPS)? When this value is true, all
|
29
|
+
# requests are sent using HTTPS. When this value is false, standard HTTP
|
30
|
+
# is used.
|
31
|
+
attr_accessor :secure_http
|
32
|
+
|
33
|
+
# Enable debugging messages? When this value is true, debug logging
|
34
|
+
# messages describing AWS communication messages are printed to standard
|
35
|
+
# output.
|
36
|
+
attr_accessor :debug
|
37
|
+
|
38
|
+
# The approximate difference in the current time between your computer and
|
39
|
+
# Amazon's servers, measured in seconds.
|
40
|
+
#
|
41
|
+
# This value is 0 by default. Use the current_time method to obtain the
|
42
|
+
# current time with this offset factor included, and the adjust_time
|
43
|
+
# method to calculate an offset value for your computer based on a
|
44
|
+
# response from an AWS server.
|
45
|
+
attr_reader :time_offset
|
46
|
+
|
47
|
+
#def initialize(access_key, secret_key, secure_http=true, debug=false)
|
48
|
+
# @aws_access_key = access_key
|
49
|
+
# @aws_secret_key = secret_key
|
50
|
+
# @time_offset = 0
|
51
|
+
# @secure_http = secure_http
|
52
|
+
# @debug = debug
|
53
|
+
#end
|
54
|
+
|
55
|
+
# Hard-coded credentials
|
56
|
+
#def initialize(secure_http=true, debug=false)
|
57
|
+
# @aws_access_key = 'YOUR_AWS_ACCESS_KEY'
|
58
|
+
# @aws_secret_key = 'YOUR_AWS_SECRET_KEY'
|
59
|
+
# @time_offset = 0
|
60
|
+
# @secure_http = secure_http
|
61
|
+
# @debug = debug
|
62
|
+
#end
|
63
|
+
|
64
|
+
# Initialize AWS and set the service-specific variables: aws_access_key,
|
65
|
+
# aws_secret_key, debug, and secure_http.
|
66
|
+
def initialize(aws_access_key=ENV['AWS_ACCESS_KEY'],
|
67
|
+
aws_secret_key=ENV['AWS_SECRET_KEY'],
|
68
|
+
secure_http=true, debug=false)
|
69
|
+
@aws_access_key = aws_access_key
|
70
|
+
@aws_secret_key = aws_secret_key
|
71
|
+
@time_offset = 0
|
72
|
+
@secure_http = secure_http
|
73
|
+
@debug = debug
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
# An exception object that captures information about an AWS service error.
|
78
|
+
class ServiceError < RuntimeError
|
79
|
+
attr_accessor :response, :aws_error_xml
|
80
|
+
|
81
|
+
# Initialize a ServiceError object based on an HTTP Response
|
82
|
+
def initialize(http_response)
|
83
|
+
# Store the HTTP response as a class variable
|
84
|
+
@response = http_response
|
85
|
+
|
86
|
+
# Add the HTTP status code and message to a descriptive message
|
87
|
+
message = "HTTP Error: #{@response.code} - #{@response.message}"
|
88
|
+
|
89
|
+
# If an AWS error message is available, add its code and message
|
90
|
+
# to the overall descriptive message
|
91
|
+
if @response.body and @response.body.index('<?xml') == 0
|
92
|
+
@aws_error_xml = REXML::Document.new(@response.body)
|
93
|
+
|
94
|
+
aws_error_code = @aws_error_xml.elements['//Code'].text
|
95
|
+
aws_error_message = @aws_error_xml.elements['//Message'].text
|
96
|
+
|
97
|
+
message += ", AWS Error: #{aws_error_code} - #{aws_error_message}"
|
98
|
+
end
|
99
|
+
|
100
|
+
# Initialize the RuntimeError superclass with the descriptive message
|
101
|
+
super(message)
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
# Generates an AWS signature value for the given request description.
|
108
|
+
# The result value is a HMAC signature that is cryptographically signed
|
109
|
+
# with the SHA1 algorithm using your AWS Secret Key credential. The
|
110
|
+
# signature value is Base64 encoded before being returned.
|
111
|
+
#
|
112
|
+
# This method can be used to sign requests destined for the REST or
|
113
|
+
# Query AWS API interfaces.
|
114
|
+
def generate_signature(request_description)
|
115
|
+
raise "aws_access_key is not set" if not @aws_access_key
|
116
|
+
raise "aws_secret_key is not set" if not @aws_secret_key
|
117
|
+
|
118
|
+
digest_generator = OpenSSL::Digest::Digest.new('sha1')
|
119
|
+
digest = OpenSSL::HMAC.digest(digest_generator,
|
120
|
+
@aws_secret_key,
|
121
|
+
request_description)
|
122
|
+
b64_sig = encode_base64(digest)
|
123
|
+
return b64_sig
|
124
|
+
end
|
125
|
+
|
126
|
+
|
127
|
+
# Converts a minimal set of parameters destined for an AWS Query API
|
128
|
+
# interface into a complete set necessary for invoking an AWS operation.
|
129
|
+
#
|
130
|
+
# Normal parameters are included in the resultant complete set as-is.
|
131
|
+
#
|
132
|
+
# Indexed parameters are converted into multiple parameter name/value
|
133
|
+
# pairs, where the name starts with the given parameter name but has a
|
134
|
+
# suffix value appended to it. For example, the input mapping
|
135
|
+
# 'Name' => ['x','y']
|
136
|
+
# will be converted to two parameters:
|
137
|
+
# 'Name.1' => 'x'
|
138
|
+
# 'Name.2' => 'y'
|
139
|
+
def build_query_params(api_ver, sig_ver, params, indexed_params={}, indexed_start=1)
|
140
|
+
# Set mandatory query parameters
|
141
|
+
built_params = {
|
142
|
+
'Version' => api_ver,
|
143
|
+
'SignatureVersion' => sig_ver,
|
144
|
+
'AWSAccessKeyId' => @aws_access_key
|
145
|
+
}
|
146
|
+
|
147
|
+
# Use current time as timestamp if no date/time value is already set
|
148
|
+
if params['Timestamp'].nil? and params['Expires'].nil?
|
149
|
+
params['Timestamp'] = current_time.getutc.iso8601
|
150
|
+
end
|
151
|
+
|
152
|
+
# Merge parameters provided with defaults after removing
|
153
|
+
# any parameters without a value.
|
154
|
+
built_params.merge!(params.reject {|name,value| value.nil?})
|
155
|
+
|
156
|
+
# Add any indexed parameters as ParamName.1, ParamName.2, etc
|
157
|
+
indexed_params.each do |param_name,value_array|
|
158
|
+
index_count = indexed_start
|
159
|
+
value_array.each do |value|
|
160
|
+
built_params["#{param_name}.#{index_count}"] = value
|
161
|
+
index_count += 1
|
162
|
+
end if value_array
|
163
|
+
end
|
164
|
+
|
165
|
+
return built_params
|
166
|
+
end
|
167
|
+
|
168
|
+
|
169
|
+
# Sends a GET or POST request message to an AWS service's Query API
|
170
|
+
# interface and returns the response result from the service. This method
|
171
|
+
# signs the request message with your AWS credentials.
|
172
|
+
#
|
173
|
+
# If the AWS service returns an error message, this method will throw a
|
174
|
+
# ServiceException describing the error.
|
175
|
+
def do_query(method, uri, parameters)
|
176
|
+
# Ensure the URI is using Secure HTTP protocol if the flag is set
|
177
|
+
if @secure_http
|
178
|
+
uri.scheme = 'https'
|
179
|
+
uri.port = 443
|
180
|
+
else
|
181
|
+
uri.scheme = 'http'
|
182
|
+
uri.port = 80
|
183
|
+
end
|
184
|
+
|
185
|
+
# Generate request description and signature, and add to the request
|
186
|
+
# as the parameter 'Signature'
|
187
|
+
req_desc = parameters.sort {|x,y| x[0].downcase <=> y[0].downcase}.to_s
|
188
|
+
signature = generate_signature(req_desc)
|
189
|
+
parameters['Signature'] = signature
|
190
|
+
|
191
|
+
case method
|
192
|
+
when 'GET'
|
193
|
+
# Create GET request with parameters in URI
|
194
|
+
uri.query = ''
|
195
|
+
parameters.each do |name, value|
|
196
|
+
uri.query << "#{name}=#{CGI::escape(value.to_s)}&"
|
197
|
+
end
|
198
|
+
req = Net::HTTP::Get.new(uri.request_uri)
|
199
|
+
when 'POST'
|
200
|
+
# Create POST request with parameters in form data
|
201
|
+
req = Net::HTTP::Post.new(uri.request_uri)
|
202
|
+
req.set_form_data(parameters)
|
203
|
+
req.set_content_type('application/x-www-form-urlencoded',
|
204
|
+
{'charset', 'utf-8'})
|
205
|
+
else
|
206
|
+
raise "Invalid HTTP Query method: #{method}"
|
207
|
+
end
|
208
|
+
|
209
|
+
# Setup HTTP connection, optionally with SSL security
|
210
|
+
Net::HTTP.version_1_1
|
211
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
212
|
+
if @secure_http
|
213
|
+
http.use_ssl = true
|
214
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
215
|
+
end
|
216
|
+
|
217
|
+
debug_request(method, uri, {}, parameters) if @debug
|
218
|
+
|
219
|
+
response = http.request(req)
|
220
|
+
|
221
|
+
debug_response(response) if @debug
|
222
|
+
|
223
|
+
if response.is_a?(Net::HTTPSuccess)
|
224
|
+
return response
|
225
|
+
else
|
226
|
+
raise ServiceError.new(response)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
|
231
|
+
# Generates a request description string for a request destined for a REST
|
232
|
+
# AWS API interface, and returns a signature value for the request.
|
233
|
+
#
|
234
|
+
# This method will work for any REST AWS request, though it is intended
|
235
|
+
# mainly for the S3 service's API and handles special cases required for
|
236
|
+
# this service.
|
237
|
+
def generate_rest_signature(method, uri, headers)
|
238
|
+
# Set mandatory Date header if it is missing
|
239
|
+
headers['Date'] = current_time.httpdate if not headers['Date']
|
240
|
+
|
241
|
+
# Describe main components of REST request
|
242
|
+
req_desc =
|
243
|
+
"#{method}\n" +
|
244
|
+
"#{headers['Content-MD5']}\n" +
|
245
|
+
"#{headers['Content-Type']}\n" +
|
246
|
+
"#{headers['Date']}\n"
|
247
|
+
|
248
|
+
# Find any x-amz-* headers, sort them and append to the description
|
249
|
+
amz_headers = headers.reject {|name,value| name.index('x-amz-') != 0}
|
250
|
+
amz_headers = amz_headers.sort {|x, y| x[0] <=> y[0]}
|
251
|
+
amz_headers.each {|name,value| req_desc << "#{name.downcase}:#{value}\n"}
|
252
|
+
|
253
|
+
path = ''
|
254
|
+
|
255
|
+
# Handle special case of S3 alternative hostname URIs. The bucket
|
256
|
+
# portion of alternative hostnames must be included in the request
|
257
|
+
# description's URI path.
|
258
|
+
if not ['s3.amazonaws.com', 'queue.amazonaws.com'].include?(uri.host)
|
259
|
+
if uri.host =~ /(.*).s3.amazonaws.com/
|
260
|
+
path << '/' + $1
|
261
|
+
else
|
262
|
+
path << '/' + uri.host
|
263
|
+
end
|
264
|
+
# For alternative hosts, the path must end with a slash if there is
|
265
|
+
# no object in the path.
|
266
|
+
path << '/' if uri.path == ''
|
267
|
+
end
|
268
|
+
|
269
|
+
# Append the request's URI path to the description
|
270
|
+
path << uri.path
|
271
|
+
|
272
|
+
# Ensure the request description's URI path includes at least a slash.
|
273
|
+
if path == ''
|
274
|
+
req_desc << '/'
|
275
|
+
else
|
276
|
+
req_desc << path
|
277
|
+
end
|
278
|
+
|
279
|
+
# Append special S3 parameters to request description
|
280
|
+
if uri.query
|
281
|
+
uri.query.split('&').each do |param|
|
282
|
+
if ['acl', 'torrent', 'logging', 'location'].include?(param)
|
283
|
+
req_desc << "?" + param
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
if @debug
|
289
|
+
puts "REQUEST DESCRIPTION\n======="
|
290
|
+
puts "#{req_desc.gsub("\n","\\n\n")}\n\n"
|
291
|
+
end
|
292
|
+
|
293
|
+
# Generate signature
|
294
|
+
return generate_signature(req_desc)
|
295
|
+
end
|
296
|
+
|
297
|
+
|
298
|
+
# Sends a GET, HEAD, DELETE or PUT request message to an AWS service's
|
299
|
+
# REST API interface and returns the response result from the service. This
|
300
|
+
# method signs the request message with your AWS credentials.
|
301
|
+
#
|
302
|
+
# If the AWS service returns an error message, this method will throw a
|
303
|
+
# ServiceException describing the error. This method also includes support
|
304
|
+
# for following Temporary Redirect responses (with HTTP response
|
305
|
+
# codes 307).
|
306
|
+
def do_rest(method, uri, data=nil, headers={})
|
307
|
+
# Generate request description and signature, and add to the request
|
308
|
+
# as the header 'Authorization'
|
309
|
+
signature = generate_rest_signature(method, uri, headers)
|
310
|
+
headers['Authorization'] = "AWS #{@aws_access_key}:#{signature}"
|
311
|
+
|
312
|
+
# Ensure the Host header is always set
|
313
|
+
headers['Host'] = uri.host
|
314
|
+
|
315
|
+
# Tell service to confirm the request message is valid before it
|
316
|
+
# accepts data. Confirmation is indicated by a 100 (Continue) message
|
317
|
+
headers['Expect'] = '100-continue' if method == 'PUT'
|
318
|
+
|
319
|
+
redirect_count = 0
|
320
|
+
while redirect_count < 5 # Repeat requests after a 307 Temporary Redirect
|
321
|
+
|
322
|
+
# Setup a new HTTP connection, optionally with secure HTTPS enabled
|
323
|
+
Net::HTTP.version_1_1
|
324
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
325
|
+
if @secure_http
|
326
|
+
http.use_ssl = true
|
327
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
328
|
+
end
|
329
|
+
|
330
|
+
debug_request(method, uri, headers, {}, data) if @debug
|
331
|
+
|
332
|
+
# Perform the request. Uploads via the PUT method get special treatment
|
333
|
+
if method == 'PUT'
|
334
|
+
if data.respond_to?(:stat)
|
335
|
+
# Special case for file uploads, these are streamed
|
336
|
+
req = Net::HTTP::Put.new(uri.path, headers)
|
337
|
+
req.body_stream=data
|
338
|
+
response = http.request(req)
|
339
|
+
else
|
340
|
+
# Ensure HTTP content-length header is set to correct value
|
341
|
+
headers['Content-Length'] = (data.nil? ? '0' : data.length.to_s)
|
342
|
+
response = http.send_request(method, uri.request_uri, data, headers)
|
343
|
+
end
|
344
|
+
elsif method == 'GET' and block_given?
|
345
|
+
# Special case for streamed downloads
|
346
|
+
http.request_get(uri.request_uri, headers) do |response|
|
347
|
+
response.read_body {|segment| yield(segment)}
|
348
|
+
end
|
349
|
+
else
|
350
|
+
response = http.send_request(method, uri.request_uri, data, headers)
|
351
|
+
end
|
352
|
+
|
353
|
+
debug_response(response) if @debug
|
354
|
+
|
355
|
+
if response.is_a?(Net::HTTPTemporaryRedirect) # 307 Redirect
|
356
|
+
# Update the request to use the temporary redirect URI location
|
357
|
+
uri = URI.parse(response.header['location'])
|
358
|
+
redirect_count += 1 # Count to prevent infinite redirects
|
359
|
+
elsif response.is_a?(Net::HTTPSuccess)
|
360
|
+
return response
|
361
|
+
else
|
362
|
+
raise ServiceError.new(response)
|
363
|
+
end
|
364
|
+
|
365
|
+
end # End of while loop
|
366
|
+
end
|
367
|
+
|
368
|
+
|
369
|
+
# Prints detailed information about an HTTP request message to standard
|
370
|
+
# output.
|
371
|
+
def debug_request(method, uri, headers={}, query_parameters={}, data=nil)
|
372
|
+
puts "REQUEST\n======="
|
373
|
+
puts "Method : #{method}"
|
374
|
+
|
375
|
+
# Print URI
|
376
|
+
params = uri.to_s.split('&')
|
377
|
+
puts "URI : #{params.first}"
|
378
|
+
params[1..-1].each {|p| puts "\t &#{p}"} if params.length > 1
|
379
|
+
|
380
|
+
# Print Headers
|
381
|
+
if headers.length > 0
|
382
|
+
puts "Headers:"
|
383
|
+
headers.each {|n,v| puts " #{n}=#{v}"}
|
384
|
+
end
|
385
|
+
|
386
|
+
# Print Query Parameters
|
387
|
+
if query_parameters.length > 0
|
388
|
+
puts "Query Parameters:"
|
389
|
+
query_parameters.each {|n,v| puts " #{n}=#{v}"}
|
390
|
+
end
|
391
|
+
|
392
|
+
# Print Request Data
|
393
|
+
if data
|
394
|
+
puts "Request Body Data:"
|
395
|
+
if headers['Content-Type'] == 'application/xml'
|
396
|
+
# Pretty-print XML data
|
397
|
+
REXML::Document.new(data).write($stdout, 2)
|
398
|
+
else
|
399
|
+
puts data
|
400
|
+
end
|
401
|
+
data.rewind if data.respond_to?(:stat)
|
402
|
+
puts
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
406
|
+
|
407
|
+
# Prints detailed information about an HTTP response message to standard
|
408
|
+
# output.
|
409
|
+
def debug_response(response)
|
410
|
+
puts "\nRESPONSE\n========"
|
411
|
+
puts "Status : #{response.code} #{response.message}"
|
412
|
+
|
413
|
+
# Print Headers
|
414
|
+
if response.header.length > 0
|
415
|
+
puts "Headers:"
|
416
|
+
response.header.each {|n,v| puts " #{n}=#{v}"}
|
417
|
+
end
|
418
|
+
|
419
|
+
if response.body and response.body.respond_to?(:length)
|
420
|
+
puts "Body:"
|
421
|
+
if response.body.index('<?xml') == 0
|
422
|
+
# Pretty-print XML data
|
423
|
+
REXML::Document.new(response.body).write($stdout)
|
424
|
+
else
|
425
|
+
puts response.body
|
426
|
+
end
|
427
|
+
end
|
428
|
+
puts
|
429
|
+
end
|
430
|
+
|
431
|
+
|
432
|
+
# Returns the current date and time, adjusted according to the time
|
433
|
+
# offset between your computer and an AWS server (as set by the
|
434
|
+
# adjust_time method.
|
435
|
+
def current_time
|
436
|
+
if @time_offset
|
437
|
+
return Time.now + @time_offset
|
438
|
+
else
|
439
|
+
return Time.now
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
|
444
|
+
# Sets a time offset value to reflect the time difference between your
|
445
|
+
# computer's clock and the current time according to an AWS server. This
|
446
|
+
# method returns the calculated time difference and also sets the
|
447
|
+
# timeOffset variable in AWS.
|
448
|
+
#
|
449
|
+
# Ideally you should not rely on this method to overcome clock-related
|
450
|
+
# disagreements between your computer and AWS. If you computer is set
|
451
|
+
# to update its clock periodically and has the correct timezone setting
|
452
|
+
# you should never have to resort to this work-around.
|
453
|
+
def adjust_time(uri=URI.parse('http://aws.amazon.com/'))
|
454
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
455
|
+
response = http.send_request('GET', uri.request_uri)
|
456
|
+
|
457
|
+
local_time = Time.new
|
458
|
+
aws_time = Time.httpdate(response.header['Date'])
|
459
|
+
@time_offset = aws_time - local_time
|
460
|
+
|
461
|
+
puts "Time offset for AWS requests: #{@time_offset} seconds" if @debug
|
462
|
+
return @time_offset
|
463
|
+
end
|
464
|
+
|
465
|
+
|
466
|
+
# Base64-encodes a string, and removes the excess newline ('\n')
|
467
|
+
# characters inserted by the default ruby encoder.
|
468
|
+
def encode_base64(data)
|
469
|
+
return nil if not data
|
470
|
+
b64 = Base64.encode64(data)
|
471
|
+
cleaned = b64.gsub("\n","")
|
472
|
+
return cleaned
|
473
|
+
end
|
474
|
+
|
475
|
+
end
|