ahoward-helene 0.0.3

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.
Files changed (72) hide show
  1. data/Rakefile +274 -0
  2. data/helene.gemspec +26 -0
  3. data/lib/helene.rb +113 -0
  4. data/lib/helene/attempt.rb +46 -0
  5. data/lib/helene/aws.rb +50 -0
  6. data/lib/helene/config.rb +147 -0
  7. data/lib/helene/content_type.rb +15 -0
  8. data/lib/helene/content_type.yml +661 -0
  9. data/lib/helene/error.rb +12 -0
  10. data/lib/helene/logging.rb +55 -0
  11. data/lib/helene/objectpool.rb +220 -0
  12. data/lib/helene/rails.rb +21 -0
  13. data/lib/helene/rightscale/acf/right_acf_interface.rb +379 -0
  14. data/lib/helene/rightscale/awsbase/benchmark_fix.rb +39 -0
  15. data/lib/helene/rightscale/awsbase/right_awsbase.rb +803 -0
  16. data/lib/helene/rightscale/awsbase/support.rb +111 -0
  17. data/lib/helene/rightscale/ec2/right_ec2.rb +1737 -0
  18. data/lib/helene/rightscale/net_fix.rb +160 -0
  19. data/lib/helene/rightscale/right_aws.rb +71 -0
  20. data/lib/helene/rightscale/right_http_connection.rb +507 -0
  21. data/lib/helene/rightscale/s3/right_s3.rb +1094 -0
  22. data/lib/helene/rightscale/s3/right_s3_interface.rb +1180 -0
  23. data/lib/helene/rightscale/sdb/active_sdb.rb +930 -0
  24. data/lib/helene/rightscale/sdb/right_sdb_interface.rb +696 -0
  25. data/lib/helene/rightscale/sqs/right_sqs.rb +388 -0
  26. data/lib/helene/rightscale/sqs/right_sqs_gen2.rb +286 -0
  27. data/lib/helene/rightscale/sqs/right_sqs_gen2_interface.rb +444 -0
  28. data/lib/helene/rightscale/sqs/right_sqs_interface.rb +596 -0
  29. data/lib/helene/s3.rb +34 -0
  30. data/lib/helene/s3/bucket.rb +379 -0
  31. data/lib/helene/s3/grantee.rb +134 -0
  32. data/lib/helene/s3/key.rb +162 -0
  33. data/lib/helene/s3/owner.rb +16 -0
  34. data/lib/helene/sdb.rb +9 -0
  35. data/lib/helene/sdb/base.rb +1204 -0
  36. data/lib/helene/sdb/base/associations.rb +481 -0
  37. data/lib/helene/sdb/base/attributes.rb +90 -0
  38. data/lib/helene/sdb/base/connection.rb +20 -0
  39. data/lib/helene/sdb/base/error.rb +20 -0
  40. data/lib/helene/sdb/base/hooks.rb +82 -0
  41. data/lib/helene/sdb/base/literal.rb +52 -0
  42. data/lib/helene/sdb/base/logging.rb +23 -0
  43. data/lib/helene/sdb/base/transactions.rb +53 -0
  44. data/lib/helene/sdb/base/type.rb +137 -0
  45. data/lib/helene/sdb/base/types.rb +123 -0
  46. data/lib/helene/sdb/base/validations.rb +256 -0
  47. data/lib/helene/sdb/cast.rb +114 -0
  48. data/lib/helene/sdb/connection.rb +36 -0
  49. data/lib/helene/sdb/error.rb +5 -0
  50. data/lib/helene/sdb/interface.rb +412 -0
  51. data/lib/helene/sdb/sentinel.rb +15 -0
  52. data/lib/helene/sleepcycle.rb +29 -0
  53. data/lib/helene/superhash.rb +297 -0
  54. data/lib/helene/util.rb +132 -0
  55. data/test/auth.rb +31 -0
  56. data/test/helper.rb +98 -0
  57. data/test/integration/begin.rb +0 -0
  58. data/test/integration/ensure.rb +8 -0
  59. data/test/integration/s3/bucket.rb +106 -0
  60. data/test/integration/sdb/associations.rb +45 -0
  61. data/test/integration/sdb/creating.rb +13 -0
  62. data/test/integration/sdb/emptiness.rb +56 -0
  63. data/test/integration/sdb/hooks.rb +19 -0
  64. data/test/integration/sdb/limits.rb +27 -0
  65. data/test/integration/sdb/saving.rb +21 -0
  66. data/test/integration/sdb/selecting.rb +39 -0
  67. data/test/integration/sdb/types.rb +31 -0
  68. data/test/integration/sdb/validations.rb +60 -0
  69. data/test/integration/setup.rb +27 -0
  70. data/test/integration/teardown.rb +21 -0
  71. data/test/loader.rb +39 -0
  72. metadata +139 -0
@@ -0,0 +1,12 @@
1
+ module Helene
2
+ class Error < StandardError; end
3
+ class WTF < Error; end
4
+
5
+ def Helene.error!(*args, &block)
6
+ raise Error.new(*args, &block)
7
+ end
8
+
9
+ def Helene.wtf!(*args, &block)
10
+ raise WTF.new(*args, &block)
11
+ end
12
+ end
@@ -0,0 +1,55 @@
1
+ module Helene
2
+ class << Helene
3
+ def logger
4
+ @logger ||= nil
5
+ end
6
+
7
+ def logger= logger
8
+ @logger = logger.respond_to?(:debug) ? logger : logger_for(logger)
9
+ end
10
+
11
+ def logger_for(*args, &block)
12
+ defined?(Logging) ? Logging.logger(*args, &block) : Logger.new(*args, &block)
13
+ end
14
+
15
+ def log(*args, &block)
16
+ logger.send(*args, &block) if logger
17
+ end
18
+
19
+ def default_logger
20
+ begin
21
+ if defined?(Rails)
22
+ Rails.logger
23
+ else
24
+ if((helene_log = ENV['HELENE_LOG']))
25
+ case helene_log.to_s.downcase.strip
26
+ when 'stderr'
27
+ Helene.logger_for(STDERR)
28
+ when 'stdout'
29
+ Helene.logger_for(STDOUT)
30
+ else
31
+ begin
32
+ Helene.logger_for open(helene_log, 'a+')
33
+ rescue
34
+ Helene.logger_for open(helene_log, 'w+')
35
+ end
36
+ end
37
+ else
38
+ # null = test(?e, '/dev/null') ? '/dev/null' : 'NUL'
39
+ # Helene.logger_for open(null, 'w+')
40
+ NullLogger
41
+ end
42
+ end
43
+ rescue Object
44
+ NullLogger
45
+ end
46
+ end
47
+ module NullLogger
48
+ def respond_to?(*a, &b) true end
49
+ def method_missing(m, *a, &b) end
50
+ extend self
51
+ end
52
+ end
53
+
54
+ Helene.logger = Helene.default_logger
55
+ end
@@ -0,0 +1,220 @@
1
+ require 'thread'
2
+ require 'timeout'
3
+
4
+ module Helene
5
+ class ObjectPool
6
+ ObjectPool::Version = '0.0.1' unless defined?(ObjectPool::Version)
7
+ def ObjectPool.version() ObjectPool::Version end
8
+
9
+ class Error < ::StandardError; end
10
+
11
+ attr :size
12
+
13
+ def initialize(*args, &block)
14
+ @options = args.last.is_a?(Hash) ? args.pop : {}
15
+ @objects = args
16
+ @size = Integer([@options[:size]||@options['size']||4.2, @objects.size].max)
17
+ @used = {}
18
+ @mutex = Mutex.new
19
+ @cond = ConditionVariable.new
20
+ @locked = false
21
+ @block = block
22
+ end
23
+
24
+ def new_object
25
+ @block.call if @block
26
+ end
27
+
28
+ def create_object?
29
+ @objects.size < @size and @block
30
+ end
31
+
32
+ def put(object)
33
+ lock{ @objects.push(object) }
34
+ end
35
+
36
+ def get(*args, &block)
37
+ options = args.last.is_a?(Hash) ? args.pop : {}
38
+
39
+ blocking = true
40
+ blocking = options[:blocking] if options.has_key?(:blocking)
41
+ blocking = options['blocking'] if options.has_key?('blocking')
42
+
43
+ object = checkout(blocking)
44
+
45
+ if block
46
+ begin
47
+ return block.call(object)
48
+ ensure
49
+ checkin(object)
50
+ end
51
+ else
52
+ return object
53
+ end
54
+ end
55
+
56
+ def checkout(blocking=true)
57
+ lock {
58
+ loop do
59
+ if create_object?
60
+ @objects << (object = new_object())
61
+ @used[object] = Thread.current
62
+ return object
63
+ end
64
+
65
+ free = @objects.select{|object| not @used[object]}
66
+ unless free.empty?
67
+ object = free[ rand(free.size) ]
68
+ @used[object] = Thread.current
69
+ return object
70
+ end
71
+
72
+ raise Error, "no free object!" unless blocking
73
+ wait_unlocked
74
+ end
75
+ }
76
+ raise Error, "wtf!?"
77
+ end
78
+
79
+ def checkin(object)
80
+ lock {
81
+ @used.delete(object)
82
+ @cond.signal
83
+ }
84
+ end
85
+
86
+ def wait_unlocked
87
+ @cond.wait(@mutex)
88
+ end
89
+
90
+ def lock(&block)
91
+ return block.call if thread[thread_key(:lock)]
92
+ synchronize do
93
+ begin
94
+ thread[thread_key(:lock)] = true
95
+ block.call
96
+ ensure
97
+ thread[thread_key(:lock)] = false
98
+ end
99
+ end
100
+ end
101
+
102
+ def locked?
103
+ thread[thread_key(:lock)]
104
+ end
105
+
106
+ def thread
107
+ Thread.current
108
+ end
109
+
110
+ def thread_key(*suffix)
111
+ [ "ObjectPool[#{ object_id }]", *suffix ].join('.')
112
+ end
113
+
114
+ def synchronize(&block)
115
+ @mutex.synchronize(&block)
116
+ end
117
+
118
+ def each(&block)
119
+ lock{ @objects.each(&block) }
120
+ end
121
+
122
+ def objects(&block)
123
+ return @objects if locked?
124
+ raise ArgumentError, "no block" unless block
125
+ lock{ block.call(@objects) }
126
+ end
127
+ end
128
+
129
+ Objectpool = ObjectPool
130
+
131
+ def ObjectPool(*args, &block)
132
+ ObjectPool.new(*args, &block)
133
+ end
134
+
135
+ def Objectpool(*args, &block)
136
+ Objectpool.new(*args, &block)
137
+ end
138
+ end
139
+
140
+
141
+
142
+
143
+ if $0 == __FILE__
144
+
145
+ # test bitch fighting over the objects - this should run forever
146
+ #
147
+ require 'yaml'
148
+ require 'time'
149
+
150
+ n = 2**10
151
+
152
+ loop do
153
+ # configure a randomly sized pool
154
+ #
155
+ #size = [ 1, rand(42) ].max
156
+ #arrays = Array.new(size){ Array.new }
157
+ #pool = ObjectPool.new *arrays
158
+ pool = ObjectPool.new{ Array.new }
159
+
160
+ q = Queue.new
161
+
162
+ # setup the bitch fight
163
+ #
164
+ busy =
165
+ Thread.new do
166
+ Thread.current.abort_on_exception=true
167
+ loop{ pool.get{} }
168
+ end
169
+
170
+ n.times do |i|
171
+ Thread.new do
172
+ Thread.current.abort_on_exception=true
173
+ sleep rand
174
+
175
+ Thread.new do
176
+ Thread.current.abort_on_exception=true
177
+ sleep rand
178
+ pool.get{|array| array.push(i)}
179
+ q.push Thread.current.object_id
180
+ end
181
+ end
182
+ end
183
+
184
+ # wait for all threads
185
+ #
186
+ n.times{ q.pop }
187
+
188
+ # blow up if any thread still appears to be running
189
+ #
190
+ sleep(rand)
191
+
192
+ loop do
193
+ begin
194
+ q.pop(non_block=true)
195
+ fail
196
+ rescue ThreadError
197
+ break
198
+ end
199
+ end
200
+
201
+ # check our results - puke if they look to be crap
202
+ #
203
+ pool.lock do
204
+ values = pool.objects.flatten
205
+
206
+ if values.size==values.uniq.size
207
+ y 'pass' => Time.now.iso8601(2)
208
+ else
209
+ y(
210
+ 'size' => values.size,
211
+ 'uniq' => values.uniq.size,
212
+ 'fail' => Time.now.iso8601(2)
213
+ )
214
+ abort
215
+ end
216
+ end
217
+
218
+ busy.kill
219
+ end
220
+ end
@@ -0,0 +1,21 @@
1
+ module Helene
2
+ if defined?(Rails)
3
+ # allow configuration of in a rails project to be defined in
4
+ # config/helene.rb
5
+ #
6
+ if rails?
7
+ config = rails_root('config', 'helene.rb')
8
+ Kernel.load(config) if test(?s, config)
9
+ end
10
+
11
+ # register Sdb::Base::RecordNotFound with rails's exception handling
12
+ #
13
+ ActionController
14
+ ActionController::Base
15
+ ActionController::Base.rescue_responses.update({
16
+ 'Helene::Sdb::Base::RecordNotFound' => :not_found,
17
+ 'Helene::Sdb::Base::RecordInvalid' => :unprocessable_entity,
18
+ 'Helene::Sdb::Base::RecordNotSaved' => :unprocessable_entity,
19
+ })
20
+ end
21
+ end
@@ -0,0 +1,379 @@
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 = "2008-06-30"
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>:server</tt>: CloudFront service host, default: DEFAULT_HOST
97
+ # * <tt>:port</tt>: CloudFront service port, default: DEFAULT_PORT
98
+ # * <tt>:protocol</tt>: 'http' or 'https', default: DEFAULT_PROTOCOL
99
+ # * <tt>:multi_thread</tt>: true=HTTP connection per thread, false=per process
100
+ # * <tt>:logger</tt>: for log messages, default: RAILS_DEFAULT_LOGGER else STDOUT
101
+ # * <tt>:cache</tt>: true/false: caching for list_distributions method, default: false.
102
+ #
103
+ # acf = RightAws::AcfInterface.new('1E3GDYEOGFJPIT7XXXXXX','hgTHt68JY07JKUY08ftHYtERkjgtfERn57XXXXXX',
104
+ # {:multi_thread => true, :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
+ aws_access_key_id || ENV['AWS_ACCESS_KEY_ID'],
113
+ aws_secret_access_key || ENV['AWS_SECRET_ACCESS_KEY'],
114
+ params)
115
+ end
116
+
117
+ #-----------------------------------------------------------------
118
+ # Requests
119
+ #-----------------------------------------------------------------
120
+
121
+ # Generates request hash for REST API.
122
+ def generate_request(method, path, body=nil, headers={}) # :nodoc:
123
+ headers['content-type'] ||= 'text/xml' if body
124
+ headers['date'] = Time.now.httpdate
125
+ # Auth
126
+ signature = AwsUtils::sign(@aws_secret_access_key, headers['date'])
127
+ headers['Authorization'] = "AWS #{@aws_access_key_id}:#{signature}"
128
+ # Request
129
+ path = "#{@params[:default_service]}/#{API_VERSION}/#{path}"
130
+ request = "Net::HTTP::#{method.capitalize}".constantize.new(path)
131
+ request.body = body if body
132
+ # Set request headers
133
+ headers.each { |key, value| request[key.to_s] = value }
134
+ # prepare output hash
135
+ { :request => request,
136
+ :server => @params[:server],
137
+ :port => @params[:port],
138
+ :protocol => @params[:protocol] }
139
+ end
140
+
141
+ # Sends request to Amazon and parses the response.
142
+ # Raises AwsError if any banana happened.
143
+ def request_info(request, parser, &block) # :nodoc:
144
+ thread = @params[:multi_thread] ? Thread.current : Thread.main
145
+ thread[:acf_connection] ||= Rightscale::HttpConnection.new(:exception => RightAws::AwsError, :logger => @logger)
146
+ request_info_impl(thread[:acf_connection], @@bench, request, parser, &block)
147
+ end
148
+
149
+ #-----------------------------------------------------------------
150
+ # Helpers:
151
+ #-----------------------------------------------------------------
152
+
153
+ def self.escape(text) # :nodoc:
154
+ REXML::Text::normalize(text)
155
+ end
156
+
157
+ def self.unescape(text) # :nodoc:
158
+ REXML::Text::unnormalize(text)
159
+ end
160
+
161
+ def xmlns # :nodoc:
162
+ %Q{"http://#{@params[:server]}/doc/#{API_VERSION}/"}
163
+ end
164
+
165
+ def generate_call_reference # :nodoc:
166
+ result = Time.now.strftime('%Y%m%d%H%M%S')
167
+ 10.times{ result << rand(10).to_s }
168
+ result
169
+ end
170
+
171
+ def merge_headers(hash) # :nodoc:
172
+ hash[:location] = @last_response['Location'] if @last_response['Location']
173
+ hash[:e_tag] = @last_response['ETag'] if @last_response['ETag']
174
+ hash
175
+ end
176
+
177
+ #-----------------------------------------------------------------
178
+ # API Calls:
179
+ #-----------------------------------------------------------------
180
+
181
+ # List distributions.
182
+ # Returns an array of distributions or RightAws::AwsError exception.
183
+ #
184
+ # acf.list_distributions #=>
185
+ # [{:status => "Deployed",
186
+ # :domain_name => "d74zzrxmpmygb.6hops.net",
187
+ # :aws_id => "E4U91HCJHGXVC",
188
+ # :cnames => ["web1.my-awesome-site.net", "web2.my-awesome-site.net"]
189
+ # :origin => "my-bucket.s3.amazonaws.com",
190
+ # :comment => "My comments",
191
+ # :last_modified_time => Wed Sep 10 17:00:04 UTC 2008 }, ..., {...} ]
192
+ #
193
+ def list_distributions
194
+ request_hash = generate_request('GET', 'distribution')
195
+ request_cache_or_info :list_distributions, request_hash, AcfDistributionListParser, @@bench
196
+ end
197
+
198
+ # Create a new distribution.
199
+ # Returns the just created distribution or RightAws::AwsError exception.
200
+ #
201
+ # acf.create_distribution('bucket-for-k-dzreyev.s3.amazonaws.com', 'Woo-Hoo!', true, ['web1.my-awesome-site.net'] ) #=>
202
+ # {:comment => "Woo-Hoo!",
203
+ # :enabled => true,
204
+ # :location => "https://cloudfront.amazonaws.com/2008-06-30/distribution/E2REJM3VUN5RSI",
205
+ # :status => "InProgress",
206
+ # :aws_id => "E2REJM3VUN5RSI",
207
+ # :domain_name => "d3dxv71tbbt6cd.6hops.net",
208
+ # :origin => "my-bucket.s3.amazonaws.com",
209
+ # :cnames => ["web1.my-awesome-site.net"]
210
+ # :last_modified_time => Wed Sep 10 17:00:54 UTC 2008,
211
+ # :caller_reference => "200809102100536497863003"}
212
+ #
213
+ def create_distribution(origin, comment='', enabled=true, cnames=[], caller_reference=nil)
214
+ # join CNAMES
215
+ cnames_str = ''
216
+ unless cnames.blank?
217
+ cnames.to_a.each { |cname| cnames_str += "\n <CNAME>#{cname}</CNAME>" }
218
+ end
219
+ # reference
220
+ caller_reference ||= generate_call_reference
221
+ body = <<-EOXML
222
+ <?xml version="1.0" encoding="UTF-8"?>
223
+ <DistributionConfig xmlns=#{xmlns}>
224
+ <Origin>#{origin}</Origin>
225
+ <CallerReference>#{caller_reference}</CallerReference>
226
+ #{cnames_str.lstrip}
227
+ <Comment>#{AcfInterface::escape(comment.to_s)}</Comment>
228
+ <Enabled>#{enabled}</Enabled>
229
+ </DistributionConfig>
230
+ EOXML
231
+ request_hash = generate_request('POST', 'distribution', body.strip)
232
+ merge_headers(request_info(request_hash, AcfDistributionParser.new))
233
+ end
234
+
235
+ # Get a distribution's information.
236
+ # Returns a distribution's information or RightAws::AwsError exception.
237
+ #
238
+ # acf.get_distribution('E2REJM3VUN5RSI') #=>
239
+ # {:enabled => true,
240
+ # :caller_reference => "200809102100536497863003",
241
+ # :e_tag => "E39OHHU1ON65SI",
242
+ # :status => "Deployed",
243
+ # :domain_name => "d3dxv71tbbt6cd.6hops.net",
244
+ # :cnames => ["web1.my-awesome-site.net", "web2.my-awesome-site.net"]
245
+ # :aws_id => "E2REJM3VUN5RSI",
246
+ # :comment => "Woo-Hoo!",
247
+ # :origin => "my-bucket.s3.amazonaws.com",
248
+ # :last_modified_time => Wed Sep 10 17:00:54 UTC 2008 }
249
+ #
250
+ def get_distribution(aws_id)
251
+ request_hash = generate_request('GET', "distribution/#{aws_id}")
252
+ merge_headers(request_info(request_hash, AcfDistributionParser.new))
253
+ end
254
+
255
+ # Get a distribution's configuration.
256
+ # Returns a distribution's configuration or RightAws::AwsError exception.
257
+ #
258
+ # acf.get_distribution_config('E2REJM3VUN5RSI') #=>
259
+ # {:enabled => true,
260
+ # :caller_reference => "200809102100536497863003",
261
+ # :e_tag => "E39OHHU1ON65SI",
262
+ # :cnames => ["web1.my-awesome-site.net", "web2.my-awesome-site.net"]
263
+ # :comment => "Woo-Hoo!",
264
+ # :origin => "my-bucket.s3.amazonaws.com"}
265
+ #
266
+ def get_distribution_config(aws_id)
267
+ request_hash = generate_request('GET', "distribution/#{aws_id}/config")
268
+ merge_headers(request_info(request_hash, AcfDistributionConfigParser.new))
269
+ end
270
+
271
+ # Set a distribution's configuration
272
+ # (the :origin and the :caller_reference cannot be changed).
273
+ # Returns +true+ on success or RightAws::AwsError exception.
274
+ #
275
+ # config = acf.get_distribution_config('E2REJM3VUN5RSI') #=>
276
+ # {:enabled => true,
277
+ # :caller_reference => "200809102100536497863003",
278
+ # :e_tag => "E39OHHU1ON65SI",
279
+ # :cnames => ["web1.my-awesome-site.net", "web2.my-awesome-site.net"]
280
+ # :comment => "Woo-Hoo!",
281
+ # :origin => "my-bucket.s3.amazonaws.com"}
282
+ # config[:comment] = 'Olah-lah!'
283
+ # config[:enabled] = false
284
+ # acf.set_distribution_config('E2REJM3VUN5RSI', config) #=> true
285
+ #
286
+ def set_distribution_config(aws_id, config)
287
+ # join CNAMES
288
+ cnames_str = ''
289
+ unless config[:cnames].blank?
290
+ config[:cnames].to_a.each { |cname| cnames_str += "\n <CNAME>#{cname}</CNAME>" }
291
+ end
292
+ # format request's XML body
293
+ body = <<-EOXML
294
+ <?xml version="1.0" encoding="UTF-8"?>
295
+ <DistributionConfig xmlns=#{xmlns}>
296
+ <Origin>#{config[:origin]}</Origin>
297
+ <CallerReference>#{config[:caller_reference]}</CallerReference>
298
+ #{cnames_str.lstrip}
299
+ <Comment>#{AcfInterface::escape(config[:comment].to_s)}</Comment>
300
+ <Enabled>#{config[:enabled]}</Enabled>
301
+ </DistributionConfig>
302
+ EOXML
303
+ request_hash = generate_request('PUT', "distribution/#{aws_id}/config", body.strip,
304
+ 'If-Match' => config[:e_tag])
305
+ request_info(request_hash, RightHttp2xxParser.new)
306
+ end
307
+
308
+ # Delete a distribution. The enabled distribution cannot be deleted.
309
+ # Returns +true+ on success or RightAws::AwsError exception.
310
+ #
311
+ # acf.delete_distribution('E2REJM3VUN5RSI', 'E39OHHU1ON65SI') #=> true
312
+ #
313
+ def delete_distribution(aws_id, e_tag)
314
+ request_hash = generate_request('DELETE', "distribution/#{aws_id}", nil,
315
+ 'If-Match' => e_tag)
316
+ request_info(request_hash, RightHttp2xxParser.new)
317
+ end
318
+
319
+ #-----------------------------------------------------------------
320
+ # PARSERS:
321
+ #-----------------------------------------------------------------
322
+
323
+ class AcfDistributionListParser < RightAWSParser # :nodoc:
324
+ def reset
325
+ @result = []
326
+ end
327
+ def tagstart(name, attributes)
328
+ @distribution = { :cnames => [] } if name == 'DistributionSummary'
329
+ end
330
+ def tagend(name)
331
+ case name
332
+ when 'Id' then @distribution[:aws_id] = @text
333
+ when 'Status' then @distribution[:status] = @text
334
+ when 'LastModifiedTime' then @distribution[:last_modified_time] = Time.parse(@text)
335
+ when 'DomainName' then @distribution[:domain_name] = @text
336
+ when 'Origin' then @distribution[:origin] = @text
337
+ when 'Comment' then @distribution[:comment] = AcfInterface::unescape(@text)
338
+ when 'CNAME' then @distribution[:cnames] << @text
339
+ when 'DistributionSummary' then @result << @distribution
340
+ end
341
+ end
342
+ end
343
+
344
+ class AcfDistributionParser < RightAWSParser # :nodoc:
345
+ def reset
346
+ @result = { :cnames => [] }
347
+ end
348
+ def tagend(name)
349
+ case name
350
+ when 'Id' then @result[:aws_id] = @text
351
+ when 'Status' then @result[:status] = @text
352
+ when 'LastModifiedTime' then @result[:last_modified_time] = Time.parse(@text)
353
+ when 'DomainName' then @result[:domain_name] = @text
354
+ when 'Origin' then @result[:origin] = @text
355
+ when 'CallerReference' then @result[:caller_reference] = @text
356
+ when 'Comment' then @result[:comment] = AcfInterface::unescape(@text)
357
+ when 'Enabled' then @result[:enabled] = @text == 'true' ? true : false
358
+ when 'CNAME' then @result[:cnames] << @text
359
+ end
360
+ end
361
+ end
362
+
363
+ class AcfDistributionConfigParser < RightAWSParser # :nodoc:
364
+ def reset
365
+ @result = { :cnames => [] }
366
+ end
367
+ def tagend(name)
368
+ case name
369
+ when 'Origin' then @result[:origin] = @text
370
+ when 'CallerReference' then @result[:caller_reference] = @text
371
+ when 'Comment' then @result[:comment] = AcfInterface::unescape(@text)
372
+ when 'Enabled' then @result[:enabled] = @text == 'true' ? true : false
373
+ when 'CNAME' then @result[:cnames] << @text
374
+ end
375
+ end
376
+ end
377
+
378
+ end
379
+ end