redaranj-right_aws 1.10.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,413 @@
1
+ #
2
+ # Copyright (c) 2008 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #
23
+
24
+ module RightAws
25
+
26
+ # = RightAws::AcfInterface -- RightScale Amazon's CloudFront interface
27
+ # The AcfInterface class provides a complete interface to Amazon's
28
+ # CloudFront service.
29
+ #
30
+ # For explanations of the semantics of each call, please refer to
31
+ # Amazon's documentation at
32
+ # http://developer.amazonwebservices.com/connect/kbcategory.jspa?categoryID=211
33
+ #
34
+ # Example:
35
+ #
36
+ # acf = RightAws::AcfInterface.new('1E3GDYEOGFJPIT7XXXXXX','hgTHt68JY07JKUY08ftHYtERkjgtfERn57XXXXXX')
37
+ #
38
+ # list = acf.list_distributions #=>
39
+ # [{:status => "Deployed",
40
+ # :domain_name => "d74zzrxmpmygb.6hops.net",
41
+ # :aws_id => "E4U91HCJHGXVC",
42
+ # :origin => "my-bucket.s3.amazonaws.com",
43
+ # :cnames => ["x1.my-awesome-site.net", "x1.my-awesome-site.net"]
44
+ # :comment => "My comments",
45
+ # :last_modified_time => Wed Sep 10 17:00:04 UTC 2008 }, ..., {...} ]
46
+ #
47
+ # distibution = list.first
48
+ #
49
+ # info = acf.get_distribution(distibution[:aws_id]) #=>
50
+ # {:enabled => true,
51
+ # :caller_reference => "200809102100536497863003",
52
+ # :e_tag => "E39OHHU1ON65SI",
53
+ # :status => "Deployed",
54
+ # :domain_name => "d3dxv71tbbt6cd.6hops.net",
55
+ # :cnames => ["web1.my-awesome-site.net", "web2.my-awesome-site.net"]
56
+ # :aws_id => "E2REJM3VUN5RSI",
57
+ # :comment => "Woo-Hoo!",
58
+ # :origin => "my-bucket.s3.amazonaws.com",
59
+ # :last_modified_time => Wed Sep 10 17:00:54 UTC 2008 }
60
+ #
61
+ # config = acf.get_distribution_config(distibution[:aws_id]) #=>
62
+ # {:enabled => true,
63
+ # :caller_reference => "200809102100536497863003",
64
+ # :e_tag => "E39OHHU1ON65SI",
65
+ # :cnames => ["web1.my-awesome-site.net", "web2.my-awesome-site.net"]
66
+ # :comment => "Woo-Hoo!",
67
+ # :origin => "my-bucket.s3.amazonaws.com"}
68
+ #
69
+ # config[:comment] = 'Olah-lah!'
70
+ # config[:enabled] = false
71
+ # config[:cnames] << "web3.my-awesome-site.net"
72
+ #
73
+ # acf.set_distribution_config(distibution[:aws_id], config) #=> true
74
+ #
75
+ class AcfInterface < RightAwsBase
76
+
77
+ include RightAwsBaseInterface
78
+
79
+ API_VERSION = "2009-04-02"
80
+ DEFAULT_HOST = 'cloudfront.amazonaws.com'
81
+ DEFAULT_PORT = 443
82
+ DEFAULT_PROTOCOL = 'https'
83
+ DEFAULT_PATH = '/'
84
+
85
+ @@bench = AwsBenchmarkingBlock.new
86
+ def self.bench_xml
87
+ @@bench.xml
88
+ end
89
+ def self.bench_service
90
+ @@bench.service
91
+ end
92
+
93
+ # Create a new handle to a CloudFront account. All handles share the same per process or per thread
94
+ # HTTP connection to CloudFront. Each handle is for a specific account. The params have the
95
+ # following options:
96
+ # * <tt>:endpoint_url</tt> a fully qualified url to Amazon API endpoint (this overwrites: :server, :port, :service, :protocol). Example: 'https://cloudfront.amazonaws.com'
97
+ # * <tt>:server</tt>: CloudFront service host, default: DEFAULT_HOST
98
+ # * <tt>:port</tt>: CloudFront service port, default: DEFAULT_PORT
99
+ # * <tt>:protocol</tt>: 'http' or 'https', default: DEFAULT_PROTOCOL
100
+ # * <tt>:multi_thread</tt>: true=HTTP connection per thread, false=per process
101
+ # * <tt>:logger</tt>: for log messages, default: RAILS_DEFAULT_LOGGER else STDOUT
102
+ #
103
+ # acf = RightAws::AcfInterface.new('1E3GDYEOGFJPIT7XXXXXX','hgTHt68JY07JKUY08ftHYtERkjgtfERn57XXXXXX',
104
+ # {:logger => Logger.new('/tmp/x.log')}) #=> #<RightAws::AcfInterface::0xb7b3c30c>
105
+ #
106
+ def initialize(aws_access_key_id=nil, aws_secret_access_key=nil, params={})
107
+ init({ :name => 'ACF',
108
+ :default_host => ENV['ACF_URL'] ? URI.parse(ENV['ACF_URL']).host : DEFAULT_HOST,
109
+ :default_port => ENV['ACF_URL'] ? URI.parse(ENV['ACF_URL']).port : DEFAULT_PORT,
110
+ :default_service => ENV['ACF_URL'] ? URI.parse(ENV['ACF_URL']).path : DEFAULT_PATH,
111
+ :default_protocol => ENV['ACF_URL'] ? URI.parse(ENV['ACF_URL']).scheme : DEFAULT_PROTOCOL,
112
+ :default_api_version => ENV['ACF_API_VERSION'] || API_VERSION },
113
+ aws_access_key_id || ENV['AWS_ACCESS_KEY_ID'],
114
+ aws_secret_access_key || ENV['AWS_SECRET_ACCESS_KEY'],
115
+ params)
116
+ end
117
+
118
+ #-----------------------------------------------------------------
119
+ # Requests
120
+ #-----------------------------------------------------------------
121
+
122
+ # Generates request hash for REST API.
123
+ def generate_request(method, path, params={}, body=nil, headers={}) # :nodoc:
124
+ # Params
125
+ params.delete_if{ |key, val| val.blank? }
126
+ unless params.blank?
127
+ path += "?" + params.to_a.collect{ |key,val| "#{AwsUtils::amz_escape(key)}=#{AwsUtils::amz_escape(val.to_s)}" }.join("&")
128
+ end
129
+ # Headers
130
+ headers['content-type'] ||= 'text/xml' if body
131
+ headers['date'] = Time.now.httpdate
132
+ # Auth
133
+ signature = AwsUtils::sign(@aws_secret_access_key, headers['date'])
134
+ headers['Authorization'] = "AWS #{@aws_access_key_id}:#{signature}"
135
+ # Request
136
+ path = "#{@params[:service]}#{@params[:api_version]}/#{path}"
137
+ request = "Net::HTTP::#{method.capitalize}".constantize.new(path)
138
+ request.body = body if body
139
+ # Set request headers
140
+ headers.each { |key, value| request[key.to_s] = value }
141
+ # prepare output hash
142
+ { :request => request,
143
+ :server => @params[:server],
144
+ :port => @params[:port],
145
+ :protocol => @params[:protocol] }
146
+ end
147
+
148
+ # Sends request to Amazon and parses the response.
149
+ # Raises AwsError if any banana happened.
150
+ def request_info(request, parser, &block) # :nodoc:
151
+ request_info_impl(:acf_connection, @@bench, request, parser, &block)
152
+ end
153
+
154
+ #-----------------------------------------------------------------
155
+ # Helpers:
156
+ #-----------------------------------------------------------------
157
+
158
+ def self.escape(text) # :nodoc:
159
+ REXML::Text::normalize(text)
160
+ end
161
+
162
+ def self.unescape(text) # :nodoc:
163
+ REXML::Text::unnormalize(text)
164
+ end
165
+
166
+ def generate_call_reference # :nodoc:
167
+ result = Time.now.strftime('%Y%m%d%H%M%S')
168
+ 10.times{ result << rand(10).to_s }
169
+ result
170
+ end
171
+
172
+ def merge_headers(hash) # :nodoc:
173
+ hash[:location] = @last_response['Location'] if @last_response['Location']
174
+ hash[:e_tag] = @last_response['ETag'] if @last_response['ETag']
175
+ hash
176
+ end
177
+
178
+ def config_to_xml(config) # :nodoc:
179
+ cnames = ''
180
+ unless config[:cnames].blank?
181
+ config[:cnames].to_a.each { |cname| cnames += " <CNAME>#{cname}</CNAME>\n" }
182
+ end
183
+ # logging
184
+ logging = ''
185
+ unless config[:logging].blank?
186
+ logging = " <Logging>\n" +
187
+ " <Bucket>#{config[:logging][:bucket]}</Bucket>\n" +
188
+ " <Prefix>#{config[:logging][:prefix]}</Prefix>\n" +
189
+ " </Logging>\n"
190
+ end
191
+ # xml
192
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
193
+ "<DistributionConfig xmlns=\"http://#{@params[:server]}/doc/#{API_VERSION}/\">\n" +
194
+ " <Origin>#{config[:origin]}</Origin>\n" +
195
+ " <CallerReference>#{config[:caller_reference]}</CallerReference>\n" +
196
+ " <Comment>#{AcfInterface::escape(config[:comment].to_s)}</Comment>\n" +
197
+ " <Enabled>#{config[:enabled]}</Enabled>\n" +
198
+ cnames +
199
+ logging +
200
+ "</DistributionConfig>"
201
+ end
202
+
203
+ #-----------------------------------------------------------------
204
+ # API Calls:
205
+ #-----------------------------------------------------------------
206
+
207
+ # List all distributions.
208
+ # Returns an array of distributions or RightAws::AwsError exception.
209
+ #
210
+ # acf.list_distributions #=>
211
+ # [{:status => "Deployed",
212
+ # :domain_name => "d74zzrxmpmygb.6hops.net",
213
+ # :aws_id => "E4U91HCJHGXVC",
214
+ # :cnames => ["web1.my-awesome-site.net", "web2.my-awesome-site.net"]
215
+ # :origin => "my-bucket.s3.amazonaws.com",
216
+ # :comment => "My comments",
217
+ # :last_modified_time => Wed Sep 10 17:00:04 UTC 2008 }, ..., {...} ]
218
+ #
219
+ def list_distributions
220
+ result = []
221
+ incrementally_list_distributions do |response|
222
+ result += response[:distributions]
223
+ true
224
+ end
225
+ result
226
+ end
227
+
228
+ # Incrementally list distributions.
229
+ #
230
+ # Optional params: +:marker+ and +:max_items+.
231
+ #
232
+ # # get first distribution
233
+ # incrementally_list_distributions(:max_items => 1) #=>
234
+ # {:distributions=>
235
+ # [{:status=>"Deployed",
236
+ # :aws_id=>"E2Q0AOOMFNPSYL",
237
+ # :logging=>{},
238
+ # :origin=>"my-bucket.s3.amazonaws.com",
239
+ # :domain_name=>"d1s5gmdtmafnre.6hops.net",
240
+ # :comment=>"ONE LINE OF COMMENT",
241
+ # :last_modified_time=>Wed Oct 22 19:31:23 UTC 2008,
242
+ # :enabled=>true,
243
+ # :cnames=>[]}],
244
+ # :is_truncated=>true,
245
+ # :max_items=>1,
246
+ # :marker=>"",
247
+ # :next_marker=>"E2Q0AOOMFNPSYL"}
248
+ #
249
+ # # get max 100 distributions (the list will be restricted by a default MaxItems value ==100 )
250
+ # incrementally_list_distributions
251
+ #
252
+ # # list distributions by 10
253
+ # incrementally_list_distributions(:max_items => 10) do |response|
254
+ # puts response.inspect # a list of 10 distributions
255
+ # false # return false if the listing should be broken otherwise use true
256
+ # end
257
+ #
258
+ def incrementally_list_distributions(params={}, &block)
259
+ opts = {}
260
+ opts['MaxItems'] = params[:max_items] if params[:max_items]
261
+ opts['Marker'] = params[:marker] if params[:marker]
262
+ last_response = nil
263
+ loop do
264
+ link = generate_request('GET', 'distribution', opts)
265
+ last_response = request_info(link, AcfDistributionListParser.new(:logger => @logger))
266
+ opts['Marker'] = last_response[:next_marker]
267
+ break unless block && block.call(last_response) && !last_response[:next_marker].blank?
268
+ end
269
+ last_response
270
+ end
271
+
272
+ # Create a new distribution.
273
+ # Returns the just created distribution or RightAws::AwsError exception.
274
+ #
275
+ # acf.create_distribution('my-bucket.s3.amazonaws.com', 'Woo-Hoo!', true, ['web1.my-awesome-site.net'],
276
+ # { :prefix=>"log/", :bucket=>"my-logs.s3.amazonaws.com" } ) #=>
277
+ # {:comment => "Woo-Hoo!",
278
+ # :enabled => true,
279
+ # :location => "https://cloudfront.amazonaws.com/2008-06-30/distribution/E2REJM3VUN5RSI",
280
+ # :status => "InProgress",
281
+ # :aws_id => "E2REJM3VUN5RSI",
282
+ # :domain_name => "d3dxv71tbbt6cd.6hops.net",
283
+ # :origin => "my-bucket.s3.amazonaws.com",
284
+ # :cnames => ["web1.my-awesome-site.net"],
285
+ # :logging => { :prefix => "log/",
286
+ # :bucket => "my-logs.s3.amazonaws.com"},
287
+ # :last_modified_time => Wed Sep 10 17:00:54 UTC 2008,
288
+ # :caller_reference => "200809102100536497863003"}
289
+ #
290
+ def create_distribution(origin, comment='', enabled=true, cnames=[], caller_reference=nil, logging={})
291
+ config = { :origin => origin,
292
+ :comment => comment,
293
+ :enabled => enabled,
294
+ :cnames => cnames.to_a,
295
+ :caller_reference => caller_reference }
296
+ config[:logging] = logging unless logging.blank?
297
+ create_distribution_by_config(config)
298
+ end
299
+
300
+ def create_distribution_by_config(config)
301
+ config[:caller_reference] ||= generate_call_reference
302
+ link = generate_request('POST', 'distribution', {}, config_to_xml(config))
303
+ merge_headers(request_info(link, AcfDistributionListParser.new(:logger => @logger))[:distributions].first)
304
+ end
305
+
306
+ # Get a distribution's information.
307
+ # Returns a distribution's information or RightAws::AwsError exception.
308
+ #
309
+ # acf.get_distribution('E2REJM3VUN5RSI') #=>
310
+ # {:enabled => true,
311
+ # :caller_reference => "200809102100536497863003",
312
+ # :e_tag => "E39OHHU1ON65SI",
313
+ # :status => "Deployed",
314
+ # :domain_name => "d3dxv71tbbt6cd.6hops.net",
315
+ # :cnames => ["web1.my-awesome-site.net", "web2.my-awesome-site.net"]
316
+ # :aws_id => "E2REJM3VUN5RSI",
317
+ # :comment => "Woo-Hoo!",
318
+ # :origin => "my-bucket.s3.amazonaws.com",
319
+ # :last_modified_time => Wed Sep 10 17:00:54 UTC 2008 }
320
+ #
321
+ def get_distribution(aws_id)
322
+ link = generate_request('GET', "distribution/#{aws_id}")
323
+ merge_headers(request_info(link, AcfDistributionListParser.new(:logger => @logger))[:distributions].first)
324
+ end
325
+
326
+ # Get a distribution's configuration.
327
+ # Returns a distribution's configuration or RightAws::AwsError exception.
328
+ #
329
+ # acf.get_distribution_config('E2REJM3VUN5RSI') #=>
330
+ # {:enabled => true,
331
+ # :caller_reference => "200809102100536497863003",
332
+ # :e_tag => "E39OHHU1ON65SI",
333
+ # :cnames => ["web1.my-awesome-site.net", "web2.my-awesome-site.net"]
334
+ # :comment => "Woo-Hoo!",
335
+ # :origin => "my-bucket.s3.amazonaws.com"}
336
+ #
337
+ def get_distribution_config(aws_id)
338
+ link = generate_request('GET', "distribution/#{aws_id}/config")
339
+ merge_headers(request_info(link, AcfDistributionListParser.new(:logger => @logger))[:distributions].first)
340
+ end
341
+
342
+ # Set a distribution's configuration
343
+ # (the :origin and the :caller_reference cannot be changed).
344
+ # Returns +true+ on success or RightAws::AwsError exception.
345
+ #
346
+ # config = acf.get_distribution_config('E2REJM3VUN5RSI') #=>
347
+ # {:enabled => true,
348
+ # :caller_reference => "200809102100536497863003",
349
+ # :e_tag => "E39OHHU1ON65SI",
350
+ # :cnames => ["web1.my-awesome-site.net", "web2.my-awesome-site.net"]
351
+ # :comment => "Woo-Hoo!",
352
+ # :origin => "my-bucket.s3.amazonaws.com"}
353
+ # config[:comment] = 'Olah-lah!'
354
+ # config[:enabled] = false
355
+ # acf.set_distribution_config('E2REJM3VUN5RSI', config) #=> true
356
+ #
357
+ def set_distribution_config(aws_id, config)
358
+ link = generate_request('PUT', "distribution/#{aws_id}/config", {}, config_to_xml(config),
359
+ 'If-Match' => config[:e_tag])
360
+ request_info(link, RightHttp2xxParser.new(:logger => @logger))
361
+ end
362
+
363
+ # Delete a distribution. The enabled distribution cannot be deleted.
364
+ # Returns +true+ on success or RightAws::AwsError exception.
365
+ #
366
+ # acf.delete_distribution('E2REJM3VUN5RSI', 'E39OHHU1ON65SI') #=> true
367
+ #
368
+ def delete_distribution(aws_id, e_tag)
369
+ link = generate_request('DELETE', "distribution/#{aws_id}", {}, nil,
370
+ 'If-Match' => e_tag)
371
+ request_info(link, RightHttp2xxParser.new(:logger => @logger))
372
+ end
373
+
374
+ #-----------------------------------------------------------------
375
+ # PARSERS:
376
+ #-----------------------------------------------------------------
377
+
378
+ class AcfDistributionListParser < RightAWSParser # :nodoc:
379
+ def reset
380
+ @result = { :distributions => [] }
381
+ end
382
+ def tagstart(name, attributes)
383
+ if name == 'DistributionSummary' || name == 'Distribution' ||
384
+ (name == 'DistributionConfig' && @xmlpath.blank?)
385
+ @distribution = { :cnames => [], :logging => {} }
386
+ end
387
+ end
388
+ def tagend(name)
389
+ case name
390
+ when 'Marker' then @result[:marker] = @text
391
+ when 'NextMarker' then @result[:next_marker] = @text
392
+ when 'MaxItems' then @result[:max_items] = @text.to_i
393
+ when 'IsTruncated' then @result[:is_truncated] = @text == 'true' ? true : false
394
+ when 'Id' then @distribution[:aws_id] = @text
395
+ when 'Status' then @distribution[:status] = @text
396
+ when 'LastModifiedTime' then @distribution[:last_modified_time] = Time.parse(@text)
397
+ when 'DomainName' then @distribution[:domain_name] = @text
398
+ when 'Origin' then @distribution[:origin] = @text
399
+ when 'Comment' then @distribution[:comment] = AcfInterface::unescape(@text)
400
+ when 'CallerReference' then @distribution[:caller_reference] = @text
401
+ when 'CNAME' then @distribution[:cnames] << @text
402
+ when 'Enabled' then @distribution[:enabled] = @text == 'true' ? true : false
403
+ when 'Bucket' then @distribution[:logging][:bucket] = @text
404
+ when 'Prefix' then @distribution[:logging][:prefix] = @text
405
+ end
406
+ if name == 'DistributionSummary' || name == 'Distribution' ||
407
+ (name == 'DistributionConfig' && @xmlpath.blank?)
408
+ @result[:distributions] << @distribution
409
+ end
410
+ end
411
+ end
412
+ end
413
+ end
@@ -0,0 +1,39 @@
1
+ #
2
+ # Copyright (c) 2007-2008 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #
23
+ #
24
+
25
+
26
+ # A hack because there's a bug in add! in Benchmark::Tms
27
+ module Benchmark #:nodoc:
28
+ class Tms #:nodoc:
29
+ def add!(&blk)
30
+ t = Benchmark::measure(&blk)
31
+ @utime = utime + t.utime
32
+ @stime = stime + t.stime
33
+ @cutime = cutime + t.cutime
34
+ @cstime = cstime + t.cstime
35
+ @real = real + t.real
36
+ self
37
+ end
38
+ end
39
+ end