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