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.
- data/Rakefile +274 -0
- data/helene.gemspec +26 -0
- data/lib/helene.rb +113 -0
- data/lib/helene/attempt.rb +46 -0
- data/lib/helene/aws.rb +50 -0
- data/lib/helene/config.rb +147 -0
- data/lib/helene/content_type.rb +15 -0
- data/lib/helene/content_type.yml +661 -0
- data/lib/helene/error.rb +12 -0
- data/lib/helene/logging.rb +55 -0
- data/lib/helene/objectpool.rb +220 -0
- data/lib/helene/rails.rb +21 -0
- data/lib/helene/rightscale/acf/right_acf_interface.rb +379 -0
- data/lib/helene/rightscale/awsbase/benchmark_fix.rb +39 -0
- data/lib/helene/rightscale/awsbase/right_awsbase.rb +803 -0
- data/lib/helene/rightscale/awsbase/support.rb +111 -0
- data/lib/helene/rightscale/ec2/right_ec2.rb +1737 -0
- data/lib/helene/rightscale/net_fix.rb +160 -0
- data/lib/helene/rightscale/right_aws.rb +71 -0
- data/lib/helene/rightscale/right_http_connection.rb +507 -0
- data/lib/helene/rightscale/s3/right_s3.rb +1094 -0
- data/lib/helene/rightscale/s3/right_s3_interface.rb +1180 -0
- data/lib/helene/rightscale/sdb/active_sdb.rb +930 -0
- data/lib/helene/rightscale/sdb/right_sdb_interface.rb +696 -0
- data/lib/helene/rightscale/sqs/right_sqs.rb +388 -0
- data/lib/helene/rightscale/sqs/right_sqs_gen2.rb +286 -0
- data/lib/helene/rightscale/sqs/right_sqs_gen2_interface.rb +444 -0
- data/lib/helene/rightscale/sqs/right_sqs_interface.rb +596 -0
- data/lib/helene/s3.rb +34 -0
- data/lib/helene/s3/bucket.rb +379 -0
- data/lib/helene/s3/grantee.rb +134 -0
- data/lib/helene/s3/key.rb +162 -0
- data/lib/helene/s3/owner.rb +16 -0
- data/lib/helene/sdb.rb +9 -0
- data/lib/helene/sdb/base.rb +1204 -0
- data/lib/helene/sdb/base/associations.rb +481 -0
- data/lib/helene/sdb/base/attributes.rb +90 -0
- data/lib/helene/sdb/base/connection.rb +20 -0
- data/lib/helene/sdb/base/error.rb +20 -0
- data/lib/helene/sdb/base/hooks.rb +82 -0
- data/lib/helene/sdb/base/literal.rb +52 -0
- data/lib/helene/sdb/base/logging.rb +23 -0
- data/lib/helene/sdb/base/transactions.rb +53 -0
- data/lib/helene/sdb/base/type.rb +137 -0
- data/lib/helene/sdb/base/types.rb +123 -0
- data/lib/helene/sdb/base/validations.rb +256 -0
- data/lib/helene/sdb/cast.rb +114 -0
- data/lib/helene/sdb/connection.rb +36 -0
- data/lib/helene/sdb/error.rb +5 -0
- data/lib/helene/sdb/interface.rb +412 -0
- data/lib/helene/sdb/sentinel.rb +15 -0
- data/lib/helene/sleepcycle.rb +29 -0
- data/lib/helene/superhash.rb +297 -0
- data/lib/helene/util.rb +132 -0
- data/test/auth.rb +31 -0
- data/test/helper.rb +98 -0
- data/test/integration/begin.rb +0 -0
- data/test/integration/ensure.rb +8 -0
- data/test/integration/s3/bucket.rb +106 -0
- data/test/integration/sdb/associations.rb +45 -0
- data/test/integration/sdb/creating.rb +13 -0
- data/test/integration/sdb/emptiness.rb +56 -0
- data/test/integration/sdb/hooks.rb +19 -0
- data/test/integration/sdb/limits.rb +27 -0
- data/test/integration/sdb/saving.rb +21 -0
- data/test/integration/sdb/selecting.rb +39 -0
- data/test/integration/sdb/types.rb +31 -0
- data/test/integration/sdb/validations.rb +60 -0
- data/test/integration/setup.rb +27 -0
- data/test/integration/teardown.rb +21 -0
- data/test/loader.rb +39 -0
- metadata +139 -0
data/lib/helene/error.rb
ADDED
@@ -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
|
data/lib/helene/rails.rb
ADDED
@@ -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
|