amazon-pricing 0.1.40 → 0.1.41
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/lib/amazon-pricing.rb +4 -531
- data/lib/amazon-pricing/version.rb +1 -1
- data/lib/aws-price-list.rb +227 -0
- data/lib/ec2-price-list.rb +79 -0
- data/lib/gov-cloud-price-list.rb +136 -0
- data/lib/rds-price-list.rb +133 -0
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
NjUzMDA0NDJhZWY5NjllNWI5ZTdmNmQyMjM5ZDY0ZDViOTM2NmMzMQ==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
ODJjYWM0M2NjMjY2NjIyNmQyMjUwZWQzZWE2YWM4Y2E4ZDEzOTIxNw==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
Y2I4ZjIxMTA3YTA4OGRiZTdhMDYyNDEwNTJlMDA5YzdmMjNiYTBlODUzZDNh
|
10
|
+
ZDJlYzIyZjEzNDU5OTFlNTNlZTM1Nzk0MGNhN2E0MDU1ZDM5OGZhNTA4ZTRi
|
11
|
+
ZmNlMTllNTEzZGQ0YmVhZGYyZjJmZjZmYjJmZGJkOTdmNWVjNzc=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
MjYzOWNiNjE0N2Q5NjRkYjVkYjM2ZmU2NjA4NDA4ZTBjYjQzNWQzYWRmYWNi
|
14
|
+
NTBmNGY1OTYxZDNjOGFjZGUxOWM2YTQ3YzVjZjg1NTU2MzE1ZDZkZmM2NzE2
|
15
|
+
ZjBjNWZhN2EzZGFlMmIyNzQ5ZmRlM2QxMTE5Yzc3MDYwYzk5MWM=
|
data/lib/amazon-pricing.rb
CHANGED
@@ -5,534 +5,7 @@ require 'mechanize'
|
|
5
5
|
|
6
6
|
Dir[File.join(File.dirname(__FILE__), 'amazon-pricing/*.rb')].sort.each { |lib| require lib }
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
# Author:: Joe Kinsella (mailto:joe.kinsella@gmail.com)
|
13
|
-
# Copyright:: Copyright (c) 2011-2013 CloudHealth
|
14
|
-
# License:: Distributes under the same terms as Ruby
|
15
|
-
# Home:: http://github.com/CloudHealth/amazon-pricing
|
16
|
-
#++
|
17
|
-
module AwsPricing
|
18
|
-
|
19
|
-
# PriceList provides the primary interface for retrieving AWS pricing.
|
20
|
-
# Upon instantiating a PriceList object, all the corresponding pricing
|
21
|
-
# information will be retrieved from Amazon via currently undocumented
|
22
|
-
# json APIs.
|
23
|
-
class PriceList
|
24
|
-
attr_accessor :regions
|
25
|
-
|
26
|
-
def get_region(name)
|
27
|
-
@_regions[@@Region_Lookup[name] || name]
|
28
|
-
end
|
29
|
-
|
30
|
-
def regions
|
31
|
-
@_regions.values
|
32
|
-
end
|
33
|
-
|
34
|
-
def get_instance_types
|
35
|
-
instance_types = []
|
36
|
-
@_regions.each do |region|
|
37
|
-
region.ec2_instance_types.each do |instance_type|
|
38
|
-
instance_types << instance_type
|
39
|
-
end
|
40
|
-
end
|
41
|
-
instance_types
|
42
|
-
end
|
43
|
-
|
44
|
-
def get_instance_type(region_name, api_name)
|
45
|
-
region = get_region(region_name)
|
46
|
-
raise "Region #{region_name} not found" if region.nil?
|
47
|
-
region.get_instance_type(api_name)
|
48
|
-
end
|
49
|
-
|
50
|
-
def self.fetch_url(url)
|
51
|
-
uri = URI.parse(url)
|
52
|
-
page = Net::HTTP.get_response(uri)
|
53
|
-
# Now that AWS switched from json to jsonp, remove first/last lines
|
54
|
-
body = page.body.gsub("callback(", "").reverse.sub(")", "").reverse
|
55
|
-
if body.split("\n").last == ";"
|
56
|
-
# Now remove one more line (rds is returning ";", ec2 empty line)
|
57
|
-
body = body.reverse.sub(";", "").reverse
|
58
|
-
elsif body[-1] == ";"
|
59
|
-
body.chop!
|
60
|
-
end
|
61
|
-
|
62
|
-
begin
|
63
|
-
JSON.parse(body)
|
64
|
-
rescue JSON::ParserError
|
65
|
-
# Handle "json" with keys that are not quoted
|
66
|
-
# When we get {foo: "1"} instead of {"foo": "1"}
|
67
|
-
# http://stackoverflow.com/questions/2060356/parsing-json-without-quoted-keys
|
68
|
-
JSON.parse(body.gsub(/(\w+)\s*:/, '"\1":'))
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
protected
|
73
|
-
|
74
|
-
attr_accessor :_regions
|
75
|
-
|
76
|
-
def add_region(region)
|
77
|
-
@_regions[region.name] = region
|
78
|
-
end
|
79
|
-
|
80
|
-
def find_or_create_region(name)
|
81
|
-
region = get_region(name)
|
82
|
-
if region.nil?
|
83
|
-
region = Region.new(name)
|
84
|
-
add_region(region)
|
85
|
-
end
|
86
|
-
region
|
87
|
-
end
|
88
|
-
|
89
|
-
EC2_BASE_URL = "http://a0.awsstatic.com/pricing/1/ec2/"
|
90
|
-
EBS_BASE_URL = "http://a0.awsstatic.com/pricing/1/ebs/"
|
91
|
-
RDS_BASE_URL = "http://a0.awsstatic.com/pricing/1/rds/"
|
92
|
-
|
93
|
-
# Lookup allows us to map to AWS API region names
|
94
|
-
@@Region_Lookup = {
|
95
|
-
'us-east-1' => 'us-east',
|
96
|
-
'us-west-1' => 'us-west',
|
97
|
-
'us-west-2' => 'us-west-2',
|
98
|
-
'eu-west-1' => 'eu-ireland',
|
99
|
-
'ap-southeast-1' => 'apac-sin',
|
100
|
-
'ap-southeast-2' => 'apac-syd',
|
101
|
-
'ap-northeast-1' => 'apac-tokyo',
|
102
|
-
'sa-east-1' => 'sa-east-1'
|
103
|
-
}
|
104
|
-
|
105
|
-
end
|
106
|
-
|
107
|
-
|
108
|
-
class GovCloudEc2PriceList < PriceList
|
109
|
-
GOV_CLOUD_URL = "http://aws.amazon.com/govcloud-us/pricing/ec2/"
|
110
|
-
GOV_CLOUD_EBS_URL = "http://aws.amazon.com/govcloud-us/pricing/ebs/"
|
111
|
-
|
112
|
-
def initialize
|
113
|
-
@_regions = {}
|
114
|
-
@_regions["us-gov-west-1"] = Region.new("us-gov-west-1")
|
115
|
-
InstanceType.populate_lookups
|
116
|
-
get_ec2_instance_pricing
|
117
|
-
fetch_ec2_ebs_pricing
|
118
|
-
end
|
119
|
-
|
120
|
-
protected
|
121
|
-
|
122
|
-
def get_ec2_instance_pricing
|
123
|
-
|
124
|
-
client = Mechanize.new
|
125
|
-
page = client.get(GOV_CLOUD_URL)
|
126
|
-
tables = page.search("//div[@class='aws-table section']")
|
127
|
-
create_ondemand_instances(get_rows(tables[0]))
|
128
|
-
create_ondemand_instances(get_rows(tables[1]))
|
129
|
-
|
130
|
-
for i in 2..7
|
131
|
-
create_reserved_instances(get_rows(tables[i]), :light)
|
132
|
-
end
|
133
|
-
for i in 8..13
|
134
|
-
create_reserved_instances(get_rows(tables[i]), :medium)
|
135
|
-
end
|
136
|
-
for i in 14..19
|
137
|
-
create_reserved_instances(get_rows(tables[i]), :heavy)
|
138
|
-
end
|
139
|
-
|
140
|
-
end
|
141
|
-
|
142
|
-
# e.g. [["Prices / Hour", "Amazon Linux", "RHEL", "SLES"], ["m1.small", "$0.053", "$0.083", "$0.083"]]
|
143
|
-
def create_ondemand_instances(rows)
|
144
|
-
header = rows[0]
|
145
|
-
@_regions.values.each do |region|
|
146
|
-
|
147
|
-
rows.slice(1, rows.size).each do |row|
|
148
|
-
api_name = row[0]
|
149
|
-
instance_type = region.get_ec2_instance_type(api_name)
|
150
|
-
if instance_type.nil?
|
151
|
-
api_name, name = Ec2InstanceType.get_name(nil, row[0], false)
|
152
|
-
instance_type = region.add_or_update_ec2_instance_type(api_name, name)
|
153
|
-
end
|
154
|
-
instance_type.update_pricing2(get_os(header[1]), :ondemand, row[1])
|
155
|
-
instance_type.update_pricing2(get_os(header[2]), :ondemand, row[2])
|
156
|
-
instance_type.update_pricing2(get_os(header[3]), :ondemand, row[3])
|
157
|
-
end
|
158
|
-
end
|
159
|
-
end
|
160
|
-
|
161
|
-
# e.g. [["RHEL", "1 yr Term Upfront", "1 yr TermHourly", "3 yr TermUpfront", "3 yr Term Hourly"], ["m1.small", "$68.00", "$0.099", "$105.00", "$0.098"]]
|
162
|
-
def create_reserved_instances(rows, res_type)
|
163
|
-
header = rows[0]
|
164
|
-
operating_system = get_os(header[0])
|
165
|
-
@_regions.values.each do |region|
|
166
|
-
|
167
|
-
rows.slice(1, rows.size).each do |row|
|
168
|
-
api_name = row[0]
|
169
|
-
instance_type = region.get_instance_type(api_name)
|
170
|
-
if instance_type.nil?
|
171
|
-
api_name, name = Ec2InstanceType.get_name(nil, row[0], true)
|
172
|
-
instance_type = region.add_or_update_ec2_instance_type(api_name, name)
|
173
|
-
end
|
174
|
-
instance_type.update_pricing2(operating_system, res_type, nil, row[1], row[3], row[2], row[4])
|
175
|
-
end
|
176
|
-
end
|
177
|
-
end
|
178
|
-
|
179
|
-
def fetch_ec2_ebs_pricing
|
180
|
-
client = Mechanize.new
|
181
|
-
page = client.get(GOV_CLOUD_EBS_URL)
|
182
|
-
ebs_costs = page.search("//div[@class='text section']//li")
|
183
|
-
@_regions.values.each do |region|
|
184
|
-
region.ebs_price = EbsPrice.new(region)
|
185
|
-
region.ebs_price.preferred_per_gb = get_ebs_price(ebs_costs[1])
|
186
|
-
region.ebs_price.preferred_per_iops = get_ebs_price(ebs_costs[2])
|
187
|
-
region.ebs_price.standard_per_gb = get_ebs_price(ebs_costs[3])
|
188
|
-
region.ebs_price.standard_per_million_io = get_ebs_price(ebs_costs[4])
|
189
|
-
region.ebs_price.ssd_per_gb = nil
|
190
|
-
region.ebs_price.s3_snaps_per_gb = get_ebs_price(ebs_costs[5])
|
191
|
-
end
|
192
|
-
|
193
|
-
end
|
194
|
-
|
195
|
-
# e.g. $0.065 per GB-Month of provisioned storage
|
196
|
-
def get_ebs_price(xml_element)
|
197
|
-
tokens = xml_element.text.split(" ")
|
198
|
-
tokens[0].gsub("$", "").to_f
|
199
|
-
end
|
200
|
-
|
201
|
-
def get_os(val)
|
202
|
-
case val
|
203
|
-
when "Amazon Linux"
|
204
|
-
:linux
|
205
|
-
when "RHEL"
|
206
|
-
:rhel
|
207
|
-
when "SLES"
|
208
|
-
:sles
|
209
|
-
when "Windows"
|
210
|
-
:mswin
|
211
|
-
when "Windows SQL Server Web", "Windows SQL Server Web Edition"
|
212
|
-
:mswinSQL
|
213
|
-
when "Windows SQL Server Standard", "Windows SQL Server Standard Edition"
|
214
|
-
:mswinSQLWeb
|
215
|
-
else
|
216
|
-
raise "Unable to identify operating system '#{val}'"
|
217
|
-
end
|
218
|
-
end
|
219
|
-
|
220
|
-
def get_rows(html_table)
|
221
|
-
rows = []
|
222
|
-
html_table.search(".//tr").each do |tr|
|
223
|
-
row = []
|
224
|
-
tr.search(".//td").each do |td|
|
225
|
-
row << td.inner_text.strip.sub("\n", " ").sub(" ", " ")
|
226
|
-
end
|
227
|
-
next if row.size == 1
|
228
|
-
rows << row unless row.empty?
|
229
|
-
end
|
230
|
-
rows
|
231
|
-
end
|
232
|
-
end
|
233
|
-
|
234
|
-
|
235
|
-
class GovCloudRdsPriceList < PriceList
|
236
|
-
GOV_CLOUD_URL = "http://aws.amazon.com/govcloud-us/pricing/rds/"
|
237
|
-
|
238
|
-
def initialize
|
239
|
-
@_regions = {}
|
240
|
-
@_regions["us-gov-west-1"] = Region.new("us-gov-west-1")
|
241
|
-
InstanceType.populate_lookups
|
242
|
-
get_rds_instance_pricing
|
243
|
-
end
|
244
|
-
|
245
|
-
protected
|
246
|
-
#@@DB_TYPE = [:mysql, :postgresql, :oracle, :sqlserver]
|
247
|
-
#@@RES_TYPES = [:light, :medium, :heavy]
|
248
|
-
|
249
|
-
def get_rds_instance_pricing
|
250
|
-
|
251
|
-
client = Mechanize.new
|
252
|
-
page = client.get(GOV_CLOUD_URL)
|
253
|
-
tables = page.search("//div[@class='aws-table section']")
|
254
|
-
|
255
|
-
create_ondemand_instances(:mysql, :ondemand, false, false, get_rows(tables[0]))
|
256
|
-
create_ondemand_instances(:mysql, :ondemand, true, false, get_rows(tables[1]))
|
257
|
-
# Mysql
|
258
|
-
no_multi_az_rows, multi_az_rows = get_reserved_rows(get_rows(tables[2]))
|
259
|
-
create_reserved_instances(:mysql, :light, false, false, no_multi_az_rows)
|
260
|
-
create_reserved_instances(:mysql, :light, true, false, multi_az_rows)
|
261
|
-
no_multi_az_rows, multi_az_rows = get_reserved_rows(get_rows(tables[3]))
|
262
|
-
create_reserved_instances(:mysql, :medium, false, false, no_multi_az_rows)
|
263
|
-
create_reserved_instances(:mysql, :medium, true, false, multi_az_rows)
|
264
|
-
no_multi_az_rows, multi_az_rows = get_reserved_rows(get_rows(tables[4]))
|
265
|
-
create_reserved_instances(:mysql, :heavy, false, false, no_multi_az_rows)
|
266
|
-
create_reserved_instances(:mysql, :heavy, true, false, multi_az_rows)
|
267
|
-
# Oracle
|
268
|
-
#no_multi_az_rows, multi_az_rows = get_reserved_rows(get_rows(tables[7]))
|
269
|
-
#create_reserved_instances(:oracle_se1, :ondemand, false, false, no_multi_az_rows)
|
270
|
-
#create_reserved_instances(:oracle_se1, :ondemand, true, false, multi_az_rows)
|
271
|
-
end
|
272
|
-
|
273
|
-
# e.g. [["General Purpose - Previous Generation", "Price Per Hour"], ["m1.small", "$0.090"], ["m1.medium", "$0.185"]]
|
274
|
-
def create_ondemand_instances(db_type, res_type, is_multi_az, is_byol, rows)
|
275
|
-
@_regions.values.each do |region|
|
276
|
-
# Skip header row
|
277
|
-
rows.slice(1, rows.size).each do |row|
|
278
|
-
api_name = row[0]
|
279
|
-
instance_type = region.get_rds_instance_type(api_name)
|
280
|
-
if instance_type.nil?
|
281
|
-
api_name, name = RdsInstanceType.get_name(nil, row[0], false)
|
282
|
-
instance_type = region.add_or_update_rds_instance_type(api_name, name)
|
283
|
-
end
|
284
|
-
instance_type.update_pricing2(db_type, res_type, is_multi_az, is_byol, row[1])
|
285
|
-
end
|
286
|
-
end
|
287
|
-
end
|
288
|
-
|
289
|
-
# e.g. [[" ", "1 yr Term", "3 yr Term"], [" ", "Upfront", "Hourly", "Upfront", "Hourly"], ["m1.small", "$159", "$0.035", "$249", "$0.033"]]
|
290
|
-
def create_reserved_instances(db_type, res_type, is_multi_az, is_byol, rows)
|
291
|
-
@_regions.values.each do |region|
|
292
|
-
rows.each do |row|
|
293
|
-
api_name = row[0]
|
294
|
-
instance_type = region.get_rds_instance_type(api_name)
|
295
|
-
if instance_type.nil?
|
296
|
-
api_name, name = RdsInstanceType.get_name(nil, row[0], true)
|
297
|
-
instance_type = region.add_or_update_rds_instance_type(api_name, name)
|
298
|
-
end
|
299
|
-
instance_type.update_pricing2(db_type, res_type, is_multi_az, is_byol, nil, row[1], row[3], row[2], row[4])
|
300
|
-
end
|
301
|
-
end
|
302
|
-
end
|
303
|
-
|
304
|
-
def get_reserved_rows(rows)
|
305
|
-
# Skip 2 header rows
|
306
|
-
new_rows = rows.slice(2, rows.size)
|
307
|
-
no_multi_az_rows = new_rows.slice(0, new_rows.size / 2)
|
308
|
-
multi_az_rows = new_rows.slice(new_rows.size / 2, new_rows.size / 2)
|
309
|
-
[no_multi_az_rows, multi_az_rows]
|
310
|
-
end
|
311
|
-
|
312
|
-
def get_rows(html_table)
|
313
|
-
rows = []
|
314
|
-
html_table.search(".//tr").each do |tr|
|
315
|
-
row = []
|
316
|
-
tr.search(".//td").each do |td|
|
317
|
-
row << td.inner_text.strip.sub("\n", " ").sub(" ", " ")
|
318
|
-
end
|
319
|
-
# Various <tR> elements contain labels which have only 1 <td> - except heavy multi-az ;)
|
320
|
-
next if row.size == 1 || row[0].include?("Multi-AZ Deployment")
|
321
|
-
rows << row unless row.empty?
|
322
|
-
end
|
323
|
-
rows
|
324
|
-
end
|
325
|
-
end
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
class Ec2PriceList < PriceList
|
330
|
-
|
331
|
-
def initialize
|
332
|
-
@_regions = {}
|
333
|
-
InstanceType.populate_lookups
|
334
|
-
get_ec2_on_demand_instance_pricing
|
335
|
-
get_ec2_reserved_instance_pricing
|
336
|
-
fetch_ec2_ebs_pricing
|
337
|
-
end
|
338
|
-
|
339
|
-
protected
|
340
|
-
|
341
|
-
@@OS_TYPES = [:linux, :mswin, :rhel, :sles, :mswinSQL, :mswinSQLWeb]
|
342
|
-
@@RES_TYPES = [:light, :medium, :heavy]
|
343
|
-
|
344
|
-
def get_ec2_on_demand_instance_pricing
|
345
|
-
@@OS_TYPES.each do |os|
|
346
|
-
fetch_ec2_instance_pricing(EC2_BASE_URL + "#{os}-od.min.js", :ondemand, os)
|
347
|
-
end
|
348
|
-
# Rinse & repeat for legacy instances
|
349
|
-
@@OS_TYPES.each do |os|
|
350
|
-
fetch_ec2_instance_pricing(EC2_BASE_URL + "previous-generation/#{os}-od.min.js", :ondemand, os)
|
351
|
-
end
|
352
|
-
end
|
353
|
-
|
354
|
-
def get_ec2_reserved_instance_pricing
|
355
|
-
@@OS_TYPES.each do |os|
|
356
|
-
@@RES_TYPES.each do |res_type|
|
357
|
-
fetch_ec2_instance_pricing(EC2_BASE_URL + "#{os}-ri-#{res_type}.min.js", res_type, os)
|
358
|
-
# Rinse & repeat for legacy instances (note: amazon changed URLs for legacy reserved instances)
|
359
|
-
os_rewrite = os
|
360
|
-
os_rewrite = "redhatlinux" if os == :rhel
|
361
|
-
os_rewrite = "suselinux" if os == :sles
|
362
|
-
os_rewrite = "mswinsqlstd" if os == :mswinSQL
|
363
|
-
os_rewrite = "mswinsqlweb" if os == :mswinSQLWeb
|
364
|
-
fetch_ec2_instance_pricing(EC2_BASE_URL + "previous-generation/#{res_type}_#{os_rewrite}.min.js", res_type, os)
|
365
|
-
end
|
366
|
-
end
|
367
|
-
end
|
368
|
-
|
369
|
-
# Retrieves the EC2 on-demand instance pricing.
|
370
|
-
# type_of_instance = :ondemand, :light, :medium, :heavy
|
371
|
-
def fetch_ec2_instance_pricing(url, type_of_instance, operating_system)
|
372
|
-
res = PriceList.fetch_url(url)
|
373
|
-
res['config']['regions'].each do |reg|
|
374
|
-
region_name = reg['region']
|
375
|
-
region = find_or_create_region(region_name)
|
376
|
-
# e.g. type = {"type"=>"hiCPUODI", "sizes"=>[{"size"=>"med", "valueColumns"=>[{"name"=>"mswinSQL", "prices"=>{"USD"=>"N/A"}}]}, {"size"=>"xl", "valueColumns"=>[{"name"=>"mswinSQL", "prices"=>{"USD"=>"2.427"}}]}]}
|
377
|
-
reg['instanceTypes'].each do |type|
|
378
|
-
# e.g. size = {"size"=>"xl", "valueColumns"=>[{"name"=>"mswinSQL", "prices"=>{"USD"=>"2.427"}}]}
|
379
|
-
# Amazon now can return array or hash here (hash = only 1 item)
|
380
|
-
items = type['sizes']
|
381
|
-
items = [type] if items.nil?
|
382
|
-
items.each do |size|
|
383
|
-
begin
|
384
|
-
api_name, name = Ec2InstanceType.get_name(type["type"], size["size"], type_of_instance != :ondemand)
|
385
|
-
|
386
|
-
instance_type = region.add_or_update_ec2_instance_type(api_name, name)
|
387
|
-
instance_type.update_pricing(operating_system, type_of_instance, size)
|
388
|
-
rescue UnknownTypeError
|
389
|
-
$stderr.puts "WARNING: encountered #{$!.message}"
|
390
|
-
end
|
391
|
-
end
|
392
|
-
end
|
393
|
-
end
|
394
|
-
end
|
395
|
-
|
396
|
-
def fetch_ec2_ebs_pricing
|
397
|
-
res = PriceList.fetch_url(EBS_BASE_URL + "pricing-ebs.min.js")
|
398
|
-
res["config"]["regions"].each do |ebs_types|
|
399
|
-
region = get_region(ebs_types["region"])
|
400
|
-
region.ebs_price = EbsPrice.new(region)
|
401
|
-
region.ebs_price.update_from_json(ebs_types)
|
402
|
-
end
|
403
|
-
end
|
404
|
-
|
405
|
-
end
|
406
|
-
|
407
|
-
class RdsPriceList < PriceList
|
408
|
-
|
409
|
-
def initialize
|
410
|
-
@_regions = {}
|
411
|
-
InstanceType.populate_lookups
|
412
|
-
get_rds_on_demand_instance_pricing
|
413
|
-
get_rds_reserved_instance_pricing
|
414
|
-
end
|
415
|
-
|
416
|
-
protected
|
417
|
-
|
418
|
-
@@DB_TYPE = [:mysql, :postgresql, :oracle, :sqlserver]
|
419
|
-
@@RES_TYPES = [:light, :medium, :heavy]
|
420
|
-
|
421
|
-
@@OD_DB_DEPLOY_TYPE = {
|
422
|
-
:mysql=> {:mysql=>["standard","multiAZ"]},
|
423
|
-
:postgresql=> {:postgresql=>["standard","multiAZ"]},
|
424
|
-
:oracle=> {:oracle_se1=>["li-standard","li-multiAZ","byol-standard","byol-multiAZ"], :oracle_se=>["byol-standard","byol-multiAZ"], :oracle_ee=>["byol-standard","byol-multiAZ"]},
|
425
|
-
:sqlserver=> {:sqlserver_ex=>["li-ex"], :sqlserver_web=>["li-web"], :sqlserver_se=>["li-se", "byol"], :sqlserver_ee=>["byol"]}
|
426
|
-
}
|
427
|
-
|
428
|
-
|
429
|
-
@@RESERVED_DB_DEPLOY_TYPE = {
|
430
|
-
:oracle=> {:oracle_se1=>["li","byol"], :oracle_se=>["byol"], :oracle_ee=>["byol"]},
|
431
|
-
:sqlserver=> {:sqlserver_ex=>["li-ex"], :sqlserver_web=>["li-web"], :sqlserver_se=>["li-se","byol"], :sqlserver_ee=>["byol"]}
|
432
|
-
}
|
433
|
-
|
434
|
-
|
435
|
-
def is_multi_az?(type)
|
436
|
-
return true if type.match("multiAZ")
|
437
|
-
false
|
438
|
-
end
|
439
|
-
|
440
|
-
def is_byol?(type)
|
441
|
-
return true if type.match("byol")
|
442
|
-
false
|
443
|
-
end
|
444
|
-
|
445
|
-
def get_rds_on_demand_instance_pricing
|
446
|
-
@@DB_TYPE.each do |db|
|
447
|
-
@@OD_DB_DEPLOY_TYPE[db].each {|db_type, db_instances|
|
448
|
-
db_instances.each do |dp_type|
|
449
|
-
#
|
450
|
-
# to find out the byol type
|
451
|
-
is_byol = is_byol? dp_type
|
452
|
-
|
453
|
-
if [:mysql, :postgresql, :oracle].include? db
|
454
|
-
fetch_on_demand_rds_instance_pricing(RDS_BASE_URL+"#{db}/pricing-#{dp_type}-deployments.min.js",:ondemand, db_type, is_byol)
|
455
|
-
elsif db == :sqlserver
|
456
|
-
fetch_on_demand_rds_instance_pricing(RDS_BASE_URL+"#{db}/sqlserver-#{dp_type}-ondemand.min.js",:ondemand, db_type, is_byol)
|
457
|
-
end
|
458
|
-
end
|
459
|
-
}
|
460
|
-
end
|
461
|
-
end
|
462
|
-
|
463
|
-
def get_rds_reserved_instance_pricing
|
464
|
-
@@DB_TYPE.each do |db|
|
465
|
-
if [:mysql, :postgresql].include? db
|
466
|
-
@@RES_TYPES.each do |res_type|
|
467
|
-
if db == :postgresql and res_type == :heavy
|
468
|
-
fetch_reserved_rds_instance_pricing(RDS_BASE_URL+"#{db}/pricing-#{res_type}-utilization-reserved-instances.min.js", res_type, db, false)
|
469
|
-
elsif db == :mysql
|
470
|
-
fetch_reserved_rds_instance_pricing(RDS_BASE_URL+"#{db}/pricing-#{res_type}-utilization-reserved-instances.min.js", res_type, db, false)
|
471
|
-
end
|
472
|
-
end
|
473
|
-
else
|
474
|
-
@@RESERVED_DB_DEPLOY_TYPE[db].each {|db_type, db_instance|
|
475
|
-
@@RES_TYPES.each do |res_type|
|
476
|
-
db_instance.each do |dp_type|
|
477
|
-
is_byol = is_byol? dp_type
|
478
|
-
if db == :oracle
|
479
|
-
fetch_reserved_rds_instance_pricing(RDS_BASE_URL+"#{db}/pricing-#{dp_type}-#{res_type}-utilization-reserved-instances.min.js", res_type, db_type, is_byol)
|
480
|
-
elsif db == :sqlserver
|
481
|
-
fetch_reserved_rds_instance_pricing(RDS_BASE_URL+"#{db}/sqlserver-#{dp_type}-#{res_type}-ri.min.js", res_type, db_type, is_byol)
|
482
|
-
end
|
483
|
-
end
|
484
|
-
end
|
485
|
-
}
|
486
|
-
end
|
487
|
-
end
|
488
|
-
end
|
489
|
-
|
490
|
-
def fetch_on_demand_rds_instance_pricing(url, type_of_rds_instance, db_type, is_byol)
|
491
|
-
res = PriceList.fetch_url(url)
|
492
|
-
res['config']['regions'].each do |reg|
|
493
|
-
region_name = reg['region']
|
494
|
-
region = find_or_create_region(region_name)
|
495
|
-
reg['types'].each do |type|
|
496
|
-
type['tiers'].each do |tier|
|
497
|
-
begin
|
498
|
-
#
|
499
|
-
# this is special case URL, it is oracle - multiAZ type of deployment but it doesn't have mutliAZ attributes in json.
|
500
|
-
if url == "http://aws.amazon.com/rds/pricing/oracle/pricing-li-multiAZ-deployments.min.js"
|
501
|
-
is_multi_az = true
|
502
|
-
else
|
503
|
-
is_multi_az = is_multi_az? type["name"]
|
504
|
-
end
|
505
|
-
api_name, name = RdsInstanceType.get_name(type["name"], tier["name"], type_of_rds_instance != :ondemand)
|
506
|
-
|
507
|
-
instance_type = region.add_or_update_rds_instance_type(api_name, name)
|
508
|
-
instance_type.update_pricing(db_type, type_of_rds_instance, tier, is_multi_az, is_byol)
|
509
|
-
rescue UnknownTypeError
|
510
|
-
$stderr.puts "WARNING: encountered #{$!.message}"
|
511
|
-
end
|
512
|
-
end
|
513
|
-
end
|
514
|
-
end
|
515
|
-
end
|
516
|
-
|
517
|
-
def fetch_reserved_rds_instance_pricing(url, type_of_rds_instance, db_type, is_byol)
|
518
|
-
res = PriceList.fetch_url(url)
|
519
|
-
res['config']['regions'].each do |reg|
|
520
|
-
region_name = reg['region']
|
521
|
-
region = find_or_create_region(region_name)
|
522
|
-
reg['instanceTypes'].each do |type|
|
523
|
-
type['tiers'].each do |tier|
|
524
|
-
begin
|
525
|
-
is_multi_az = is_multi_az? type["type"]
|
526
|
-
api_name, name = RdsInstanceType.get_name(type["type"], tier["size"], true)
|
527
|
-
|
528
|
-
instance_type = region.add_or_update_rds_instance_type(api_name, name)
|
529
|
-
instance_type.update_pricing(db_type, type_of_rds_instance, tier, is_multi_az, is_byol)
|
530
|
-
rescue UnknownTypeError
|
531
|
-
$stderr.puts "WARNING: encountered #{$!.message}"
|
532
|
-
end
|
533
|
-
end
|
534
|
-
end
|
535
|
-
end
|
536
|
-
end
|
537
|
-
end
|
538
|
-
end
|
8
|
+
require 'aws-price-list'
|
9
|
+
require 'ec2-price-list'
|
10
|
+
require 'gov-cloud-price-list'
|
11
|
+
require 'rds-price-list'
|
@@ -0,0 +1,227 @@
|
|
1
|
+
#--
|
2
|
+
# Amazon Web Services Pricing Ruby library
|
3
|
+
#
|
4
|
+
# Ruby Gem Name:: amazon-pricing
|
5
|
+
# Author:: Joe Kinsella (mailto:joe.kinsella@gmail.com)
|
6
|
+
# Copyright:: Copyright (c) 2011-2013 CloudHealth
|
7
|
+
# License:: Distributes under the same terms as Ruby
|
8
|
+
# Home:: http://github.com/CloudHealth/amazon-pricing
|
9
|
+
#++
|
10
|
+
module AwsPricing
|
11
|
+
|
12
|
+
# PriceList provides the primary interface for retrieving AWS pricing.
|
13
|
+
# Upon instantiating a PriceList object, all the corresponding pricing
|
14
|
+
# information will be retrieved from Amazon via currently undocumented
|
15
|
+
# json APIs.
|
16
|
+
class PriceList
|
17
|
+
attr_accessor :regions
|
18
|
+
|
19
|
+
def get_region(name)
|
20
|
+
@_regions[@@Region_Lookup[name] || name]
|
21
|
+
end
|
22
|
+
|
23
|
+
def regions
|
24
|
+
@_regions.values
|
25
|
+
end
|
26
|
+
|
27
|
+
def get_instance_types
|
28
|
+
instance_types = []
|
29
|
+
@_regions.each do |region|
|
30
|
+
region.ec2_instance_types.each do |instance_type|
|
31
|
+
instance_types << instance_type
|
32
|
+
end
|
33
|
+
end
|
34
|
+
instance_types
|
35
|
+
end
|
36
|
+
|
37
|
+
def get_instance_type(region_name, api_name)
|
38
|
+
region = get_region(region_name)
|
39
|
+
raise "Region #{region_name} not found" if region.nil?
|
40
|
+
region.get_instance_type(api_name)
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.fetch_url(url)
|
44
|
+
uri = URI.parse(url)
|
45
|
+
page = Net::HTTP.get_response(uri)
|
46
|
+
# Now that AWS switched from json to jsonp, remove first/last lines
|
47
|
+
body = page.body.gsub("callback(", "").reverse.sub(")", "").reverse
|
48
|
+
if body.split("\n").last == ";"
|
49
|
+
# Now remove one more line (rds is returning ";", ec2 empty line)
|
50
|
+
body = body.reverse.sub(";", "").reverse
|
51
|
+
elsif body[-1] == ";"
|
52
|
+
body.chop!
|
53
|
+
end
|
54
|
+
|
55
|
+
begin
|
56
|
+
JSON.parse(body)
|
57
|
+
rescue JSON::ParserError
|
58
|
+
# Handle "json" with keys that are not quoted
|
59
|
+
# When we get {foo: "1"} instead of {"foo": "1"}
|
60
|
+
# http://stackoverflow.com/questions/2060356/parsing-json-without-quoted-keys
|
61
|
+
JSON.parse(body.gsub(/(\w+)\s*:/, '"\1":'))
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
protected
|
66
|
+
|
67
|
+
attr_accessor :_regions
|
68
|
+
|
69
|
+
def add_region(region)
|
70
|
+
@_regions[region.name] = region
|
71
|
+
end
|
72
|
+
|
73
|
+
def find_or_create_region(name)
|
74
|
+
region = get_region(name)
|
75
|
+
if region.nil?
|
76
|
+
region = Region.new(name)
|
77
|
+
add_region(region)
|
78
|
+
end
|
79
|
+
region
|
80
|
+
end
|
81
|
+
|
82
|
+
EC2_BASE_URL = "http://a0.awsstatic.com/pricing/1/ec2/"
|
83
|
+
EBS_BASE_URL = "http://a0.awsstatic.com/pricing/1/ebs/"
|
84
|
+
RDS_BASE_URL = "http://a0.awsstatic.com/pricing/1/rds/"
|
85
|
+
|
86
|
+
# Lookup allows us to map to AWS API region names
|
87
|
+
@@Region_Lookup = {
|
88
|
+
'us-east-1' => 'us-east',
|
89
|
+
'us-west-1' => 'us-west',
|
90
|
+
'us-west-2' => 'us-west-2',
|
91
|
+
'eu-west-1' => 'eu-ireland',
|
92
|
+
'ap-southeast-1' => 'apac-sin',
|
93
|
+
'ap-southeast-2' => 'apac-syd',
|
94
|
+
'ap-northeast-1' => 'apac-tokyo',
|
95
|
+
'sa-east-1' => 'sa-east-1'
|
96
|
+
}
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
class GovCloudEc2PriceList < PriceList
|
102
|
+
GOV_CLOUD_URL = "http://aws.amazon.com/govcloud-us/pricing/ec2/"
|
103
|
+
GOV_CLOUD_EBS_URL = "http://aws.amazon.com/govcloud-us/pricing/ebs/"
|
104
|
+
|
105
|
+
def initialize
|
106
|
+
@_regions = {}
|
107
|
+
@_regions["us-gov-west-1"] = Region.new("us-gov-west-1")
|
108
|
+
InstanceType.populate_lookups
|
109
|
+
get_ec2_instance_pricing
|
110
|
+
fetch_ec2_ebs_pricing
|
111
|
+
end
|
112
|
+
|
113
|
+
protected
|
114
|
+
|
115
|
+
def get_ec2_instance_pricing
|
116
|
+
|
117
|
+
client = Mechanize.new
|
118
|
+
page = client.get(GOV_CLOUD_URL)
|
119
|
+
tables = page.search("//div[@class='aws-table section']")
|
120
|
+
create_ondemand_instances(get_rows(tables[0]))
|
121
|
+
create_ondemand_instances(get_rows(tables[1]))
|
122
|
+
|
123
|
+
for i in 2..7
|
124
|
+
create_reserved_instances(get_rows(tables[i]), :light)
|
125
|
+
end
|
126
|
+
for i in 8..13
|
127
|
+
create_reserved_instances(get_rows(tables[i]), :medium)
|
128
|
+
end
|
129
|
+
for i in 14..19
|
130
|
+
create_reserved_instances(get_rows(tables[i]), :heavy)
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
# e.g. [["Prices / Hour", "Amazon Linux", "RHEL", "SLES"], ["m1.small", "$0.053", "$0.083", "$0.083"]]
|
136
|
+
def create_ondemand_instances(rows)
|
137
|
+
header = rows[0]
|
138
|
+
@_regions.values.each do |region|
|
139
|
+
|
140
|
+
rows.slice(1, rows.size).each do |row|
|
141
|
+
api_name = row[0]
|
142
|
+
instance_type = region.get_ec2_instance_type(api_name)
|
143
|
+
if instance_type.nil?
|
144
|
+
api_name, name = Ec2InstanceType.get_name(nil, row[0], false)
|
145
|
+
instance_type = region.add_or_update_ec2_instance_type(api_name, name)
|
146
|
+
end
|
147
|
+
instance_type.update_pricing2(get_os(header[1]), :ondemand, row[1])
|
148
|
+
instance_type.update_pricing2(get_os(header[2]), :ondemand, row[2])
|
149
|
+
instance_type.update_pricing2(get_os(header[3]), :ondemand, row[3])
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# e.g. [["RHEL", "1 yr Term Upfront", "1 yr TermHourly", "3 yr TermUpfront", "3 yr Term Hourly"], ["m1.small", "$68.00", "$0.099", "$105.00", "$0.098"]]
|
155
|
+
def create_reserved_instances(rows, res_type)
|
156
|
+
header = rows[0]
|
157
|
+
operating_system = get_os(header[0])
|
158
|
+
@_regions.values.each do |region|
|
159
|
+
|
160
|
+
rows.slice(1, rows.size).each do |row|
|
161
|
+
api_name = row[0]
|
162
|
+
instance_type = region.get_instance_type(api_name)
|
163
|
+
if instance_type.nil?
|
164
|
+
api_name, name = Ec2InstanceType.get_name(nil, row[0], true)
|
165
|
+
instance_type = region.add_or_update_ec2_instance_type(api_name, name)
|
166
|
+
end
|
167
|
+
instance_type.update_pricing2(operating_system, res_type, nil, row[1], row[3], row[2], row[4])
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def fetch_ec2_ebs_pricing
|
173
|
+
client = Mechanize.new
|
174
|
+
page = client.get(GOV_CLOUD_EBS_URL)
|
175
|
+
ebs_costs = page.search("//div[@class='text section']//li")
|
176
|
+
@_regions.values.each do |region|
|
177
|
+
region.ebs_price = EbsPrice.new(region)
|
178
|
+
region.ebs_price.preferred_per_gb = get_ebs_price(ebs_costs[1])
|
179
|
+
region.ebs_price.preferred_per_iops = get_ebs_price(ebs_costs[2])
|
180
|
+
region.ebs_price.standard_per_gb = get_ebs_price(ebs_costs[3])
|
181
|
+
region.ebs_price.standard_per_million_io = get_ebs_price(ebs_costs[4])
|
182
|
+
region.ebs_price.ssd_per_gb = nil
|
183
|
+
region.ebs_price.s3_snaps_per_gb = get_ebs_price(ebs_costs[5])
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
187
|
+
|
188
|
+
# e.g. $0.065 per GB-Month of provisioned storage
|
189
|
+
def get_ebs_price(xml_element)
|
190
|
+
tokens = xml_element.text.split(" ")
|
191
|
+
tokens[0].gsub("$", "").to_f
|
192
|
+
end
|
193
|
+
|
194
|
+
def get_os(val)
|
195
|
+
case val
|
196
|
+
when "Amazon Linux"
|
197
|
+
:linux
|
198
|
+
when "RHEL"
|
199
|
+
:rhel
|
200
|
+
when "SLES"
|
201
|
+
:sles
|
202
|
+
when "Windows"
|
203
|
+
:mswin
|
204
|
+
when "Windows SQL Server Web", "Windows SQL Server Web Edition"
|
205
|
+
:mswinSQL
|
206
|
+
when "Windows SQL Server Standard", "Windows SQL Server Standard Edition"
|
207
|
+
:mswinSQLWeb
|
208
|
+
else
|
209
|
+
raise "Unable to identify operating system '#{val}'"
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def get_rows(html_table)
|
214
|
+
rows = []
|
215
|
+
html_table.search(".//tr").each do |tr|
|
216
|
+
row = []
|
217
|
+
tr.search(".//td").each do |td|
|
218
|
+
row << td.inner_text.strip.sub("\n", " ").sub(" ", " ")
|
219
|
+
end
|
220
|
+
next if row.size == 1
|
221
|
+
rows << row unless row.empty?
|
222
|
+
end
|
223
|
+
rows
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module AwsPricing
|
2
|
+
class Ec2PriceList < PriceList
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
@_regions = {}
|
6
|
+
InstanceType.populate_lookups
|
7
|
+
get_ec2_on_demand_instance_pricing
|
8
|
+
get_ec2_reserved_instance_pricing
|
9
|
+
fetch_ec2_ebs_pricing
|
10
|
+
end
|
11
|
+
|
12
|
+
protected
|
13
|
+
|
14
|
+
@@OS_TYPES = [:linux, :mswin, :rhel, :sles, :mswinSQL, :mswinSQLWeb]
|
15
|
+
@@RES_TYPES = [:light, :medium, :heavy]
|
16
|
+
|
17
|
+
def get_ec2_on_demand_instance_pricing
|
18
|
+
@@OS_TYPES.each do |os|
|
19
|
+
fetch_ec2_instance_pricing(EC2_BASE_URL + "#{os}-od.min.js", :ondemand, os)
|
20
|
+
end
|
21
|
+
# Rinse & repeat for legacy instances
|
22
|
+
@@OS_TYPES.each do |os|
|
23
|
+
fetch_ec2_instance_pricing(EC2_BASE_URL + "previous-generation/#{os}-od.min.js", :ondemand, os)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def get_ec2_reserved_instance_pricing
|
28
|
+
@@OS_TYPES.each do |os|
|
29
|
+
@@RES_TYPES.each do |res_type|
|
30
|
+
fetch_ec2_instance_pricing(EC2_BASE_URL + "#{os}-ri-#{res_type}.min.js", res_type, os)
|
31
|
+
# Rinse & repeat for legacy instances (note: amazon changed URLs for legacy reserved instances)
|
32
|
+
os_rewrite = os
|
33
|
+
os_rewrite = "redhatlinux" if os == :rhel
|
34
|
+
os_rewrite = "suselinux" if os == :sles
|
35
|
+
os_rewrite = "mswinsqlstd" if os == :mswinSQL
|
36
|
+
os_rewrite = "mswinsqlweb" if os == :mswinSQLWeb
|
37
|
+
fetch_ec2_instance_pricing(EC2_BASE_URL + "previous-generation/#{res_type}_#{os_rewrite}.min.js", res_type, os)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Retrieves the EC2 on-demand instance pricing.
|
43
|
+
# type_of_instance = :ondemand, :light, :medium, :heavy
|
44
|
+
def fetch_ec2_instance_pricing(url, type_of_instance, operating_system)
|
45
|
+
res = PriceList.fetch_url(url)
|
46
|
+
res['config']['regions'].each do |reg|
|
47
|
+
region_name = reg['region']
|
48
|
+
region = find_or_create_region(region_name)
|
49
|
+
# e.g. type = {"type"=>"hiCPUODI", "sizes"=>[{"size"=>"med", "valueColumns"=>[{"name"=>"mswinSQL", "prices"=>{"USD"=>"N/A"}}]}, {"size"=>"xl", "valueColumns"=>[{"name"=>"mswinSQL", "prices"=>{"USD"=>"2.427"}}]}]}
|
50
|
+
reg['instanceTypes'].each do |type|
|
51
|
+
# e.g. size = {"size"=>"xl", "valueColumns"=>[{"name"=>"mswinSQL", "prices"=>{"USD"=>"2.427"}}]}
|
52
|
+
# Amazon now can return array or hash here (hash = only 1 item)
|
53
|
+
items = type['sizes']
|
54
|
+
items = [type] if items.nil?
|
55
|
+
items.each do |size|
|
56
|
+
begin
|
57
|
+
api_name, name = Ec2InstanceType.get_name(type["type"], size["size"], type_of_instance != :ondemand)
|
58
|
+
|
59
|
+
instance_type = region.add_or_update_ec2_instance_type(api_name, name)
|
60
|
+
instance_type.update_pricing(operating_system, type_of_instance, size)
|
61
|
+
rescue UnknownTypeError
|
62
|
+
$stderr.puts "WARNING: encountered #{$!.message}"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def fetch_ec2_ebs_pricing
|
70
|
+
res = PriceList.fetch_url(EBS_BASE_URL + "pricing-ebs.min.js")
|
71
|
+
res["config"]["regions"].each do |ebs_types|
|
72
|
+
region = get_region(ebs_types["region"])
|
73
|
+
region.ebs_price = EbsPrice.new(region)
|
74
|
+
region.ebs_price.update_from_json(ebs_types)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
module AwsPricing
|
2
|
+
class GovCloudRdsPriceList < PriceList
|
3
|
+
GOV_CLOUD_URL = "http://aws.amazon.com/govcloud-us/pricing/rds/"
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@_regions = {}
|
7
|
+
@_regions["us-gov-west-1"] = Region.new("us-gov-west-1")
|
8
|
+
InstanceType.populate_lookups
|
9
|
+
get_rds_instance_pricing
|
10
|
+
end
|
11
|
+
|
12
|
+
protected
|
13
|
+
#@@DB_TYPE = [:mysql, :postgresql, :oracle, :sqlserver]
|
14
|
+
#@@RES_TYPES = [:light, :medium, :heavy]
|
15
|
+
|
16
|
+
def get_rds_instance_pricing
|
17
|
+
|
18
|
+
client = Mechanize.new
|
19
|
+
page = client.get(GOV_CLOUD_URL)
|
20
|
+
tables = page.search("//div[@class='aws-table section']")
|
21
|
+
|
22
|
+
# Mysql
|
23
|
+
create_ondemand_instances(:mysql, :ondemand, false, false, get_rows(tables[0]))
|
24
|
+
create_ondemand_instances(:mysql, :ondemand, true, false, get_rows(tables[1]))
|
25
|
+
no_multi_az_rows, multi_az_rows = get_reserved_rows(get_rows(tables[2]))
|
26
|
+
create_reserved_instances(:mysql, :light, false, false, no_multi_az_rows)
|
27
|
+
create_reserved_instances(:mysql, :light, true, false, multi_az_rows)
|
28
|
+
no_multi_az_rows, multi_az_rows = get_reserved_rows(get_rows(tables[3]))
|
29
|
+
create_reserved_instances(:mysql, :medium, false, false, no_multi_az_rows)
|
30
|
+
create_reserved_instances(:mysql, :medium, true, false, multi_az_rows)
|
31
|
+
no_multi_az_rows, multi_az_rows = get_reserved_rows(get_rows(tables[4]))
|
32
|
+
create_reserved_instances(:mysql, :heavy, false, false, no_multi_az_rows)
|
33
|
+
create_reserved_instances(:mysql, :heavy, true, false, multi_az_rows)
|
34
|
+
|
35
|
+
# Oracle
|
36
|
+
create_ondemand_instances(:oracle_se1, :ondemand, false, false, get_rows(tables[5]))
|
37
|
+
create_ondemand_instances(:oracle_se1, :ondemand, true, false, get_rows(tables[6]))
|
38
|
+
create_ondemand_instances(:oracle_se1, :ondemand, false, true, get_rows(tables[7]))
|
39
|
+
create_ondemand_instances(:oracle_se1, :ondemand, true, true, get_rows(tables[8]))
|
40
|
+
|
41
|
+
row = 9
|
42
|
+
[false, true].each do |is_byol|
|
43
|
+
[:light, :medium, :heavy].each do |res_type|
|
44
|
+
no_multi_az_rows, multi_az_rows = get_reserved_rows(get_rows(tables[9]))
|
45
|
+
create_reserved_instances(:oracle_se1, res_type, false, is_byol, no_multi_az_rows)
|
46
|
+
create_reserved_instances(:oracle_se1, res_type, true, is_byol, multi_az_rows)
|
47
|
+
row += 1
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# SQL Server
|
52
|
+
create_ondemand_instances(:sqlserver_ex, :ondemand, false, false, get_rows(tables[15]))
|
53
|
+
create_ondemand_instances(:sqlserver_web, :ondemand, false, false, get_rows(tables[16]))
|
54
|
+
create_ondemand_instances(:sqlserver_se, :ondemand, false, false, get_rows(tables[17]))
|
55
|
+
row = 18
|
56
|
+
[:light, :medium, :heavy].each do |restype|
|
57
|
+
[:sqlserver_ex, :sqlserver_web, :sqlserver_se].each do |db|
|
58
|
+
no_multi_az_rows, multi_az_rows = get_reserved_rows(get_rows(tables[row]))
|
59
|
+
create_reserved_instances(db, restype, false, false, no_multi_az_rows)
|
60
|
+
create_reserved_instances(db, restype, true, false, multi_az_rows)
|
61
|
+
row += 1
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Postgres
|
66
|
+
# Mysql
|
67
|
+
create_ondemand_instances(:postgresql, :ondemand, false, false, get_rows(tables[31]))
|
68
|
+
create_ondemand_instances(:postgresql, :ondemand, true, false, get_rows(tables[32]))
|
69
|
+
row = 33
|
70
|
+
[:light, :medium, :heavy].each do |restype|
|
71
|
+
no_multi_az_rows, multi_az_rows = get_reserved_rows(get_rows(tables[row]))
|
72
|
+
create_reserved_instances(:postgresql, restype, false, false, no_multi_az_rows)
|
73
|
+
create_reserved_instances(:postgresql, restype, true, false, multi_az_rows)
|
74
|
+
row += 1
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
# e.g. [["General Purpose - Previous Generation", "Price Per Hour"], ["m1.small", "$0.090"], ["m1.medium", "$0.185"]]
|
80
|
+
def create_ondemand_instances(db_type, res_type, is_multi_az, is_byol, rows)
|
81
|
+
@_regions.values.each do |region|
|
82
|
+
# Skip header row
|
83
|
+
rows.slice(1, rows.size).each do |row|
|
84
|
+
api_name = row[0]
|
85
|
+
instance_type = region.get_rds_instance_type(api_name)
|
86
|
+
if instance_type.nil?
|
87
|
+
api_name, name = RdsInstanceType.get_name(nil, row[0], false)
|
88
|
+
instance_type = region.add_or_update_rds_instance_type(api_name, name)
|
89
|
+
end
|
90
|
+
instance_type.update_pricing2(db_type, res_type, is_multi_az, is_byol, row[1])
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# e.g. [[" ", "1 yr Term", "3 yr Term"], [" ", "Upfront", "Hourly", "Upfront", "Hourly"], ["m1.small", "$159", "$0.035", "$249", "$0.033"]]
|
96
|
+
def create_reserved_instances(db_type, res_type, is_multi_az, is_byol, rows)
|
97
|
+
@_regions.values.each do |region|
|
98
|
+
rows.each do |row|
|
99
|
+
api_name = row[0]
|
100
|
+
unless api_name.include?("db.")
|
101
|
+
$stderr.puts "Skipping row containing non-db type: #{api_name}"
|
102
|
+
next
|
103
|
+
end
|
104
|
+
instance_type = region.get_rds_instance_type(api_name)
|
105
|
+
if instance_type.nil?
|
106
|
+
api_name, name = RdsInstanceType.get_name(nil, row[0], true)
|
107
|
+
instance_type = region.add_or_update_rds_instance_type(api_name, name)
|
108
|
+
end
|
109
|
+
instance_type.update_pricing2(db_type, res_type, is_multi_az, is_byol, nil, row[1], row[3], row[2], row[4])
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def get_reserved_rows(rows)
|
115
|
+
# Skip 2 header rows
|
116
|
+
new_rows = rows.slice(2, rows.size)
|
117
|
+
no_multi_az_rows = new_rows.slice(0, new_rows.size / 2)
|
118
|
+
multi_az_rows = new_rows.slice(new_rows.size / 2, new_rows.size / 2)
|
119
|
+
[no_multi_az_rows, multi_az_rows]
|
120
|
+
end
|
121
|
+
|
122
|
+
def get_rows(html_table)
|
123
|
+
rows = []
|
124
|
+
html_table.search(".//tr").each do |tr|
|
125
|
+
row = []
|
126
|
+
tr.search(".//td").each do |td|
|
127
|
+
row << td.inner_text.strip.sub("\n", " ").sub(" ", " ")
|
128
|
+
end
|
129
|
+
# Various <tR> elements contain labels which have only 1 <td> - except heavy multi-az ;)
|
130
|
+
next if row.size == 1 || row[0].include?("Multi-AZ Deployment")
|
131
|
+
rows << row unless row.empty?
|
132
|
+
end
|
133
|
+
rows
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
module AwsPricing
|
2
|
+
class RdsPriceList < PriceList
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
@_regions = {}
|
6
|
+
InstanceType.populate_lookups
|
7
|
+
get_rds_on_demand_instance_pricing
|
8
|
+
get_rds_reserved_instance_pricing
|
9
|
+
end
|
10
|
+
|
11
|
+
protected
|
12
|
+
|
13
|
+
@@DB_TYPE = [:mysql, :postgresql, :oracle, :sqlserver]
|
14
|
+
@@RES_TYPES = [:light, :medium, :heavy]
|
15
|
+
|
16
|
+
@@OD_DB_DEPLOY_TYPE = {
|
17
|
+
:mysql=> {:mysql=>["standard","multiAZ"]},
|
18
|
+
:postgresql=> {:postgresql=>["standard","multiAZ"]},
|
19
|
+
:oracle=> {:oracle_se1=>["li-standard","li-multiAZ","byol-standard","byol-multiAZ"], :oracle_se=>["byol-standard","byol-multiAZ"], :oracle_ee=>["byol-standard","byol-multiAZ"]},
|
20
|
+
:sqlserver=> {:sqlserver_ex=>["li-ex"], :sqlserver_web=>["li-web"], :sqlserver_se=>["li-se", "byol"], :sqlserver_ee=>["byol"]}
|
21
|
+
}
|
22
|
+
|
23
|
+
|
24
|
+
@@RESERVED_DB_DEPLOY_TYPE = {
|
25
|
+
:oracle=> {:oracle_se1=>["li","byol"], :oracle_se=>["byol"], :oracle_ee=>["byol"]},
|
26
|
+
:sqlserver=> {:sqlserver_ex=>["li-ex"], :sqlserver_web=>["li-web"], :sqlserver_se=>["li-se","byol"], :sqlserver_ee=>["byol"]}
|
27
|
+
}
|
28
|
+
|
29
|
+
|
30
|
+
def is_multi_az?(type)
|
31
|
+
return true if type.match("multiAZ")
|
32
|
+
false
|
33
|
+
end
|
34
|
+
|
35
|
+
def is_byol?(type)
|
36
|
+
return true if type.match("byol")
|
37
|
+
false
|
38
|
+
end
|
39
|
+
|
40
|
+
def get_rds_on_demand_instance_pricing
|
41
|
+
@@DB_TYPE.each do |db|
|
42
|
+
@@OD_DB_DEPLOY_TYPE[db].each {|db_type, db_instances|
|
43
|
+
db_instances.each do |dp_type|
|
44
|
+
#
|
45
|
+
# to find out the byol type
|
46
|
+
is_byol = is_byol? dp_type
|
47
|
+
|
48
|
+
if [:mysql, :postgresql, :oracle].include? db
|
49
|
+
fetch_on_demand_rds_instance_pricing(RDS_BASE_URL+"#{db}/pricing-#{dp_type}-deployments.min.js",:ondemand, db_type, is_byol)
|
50
|
+
elsif db == :sqlserver
|
51
|
+
fetch_on_demand_rds_instance_pricing(RDS_BASE_URL+"#{db}/sqlserver-#{dp_type}-ondemand.min.js",:ondemand, db_type, is_byol)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
}
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def get_rds_reserved_instance_pricing
|
59
|
+
@@DB_TYPE.each do |db|
|
60
|
+
if [:mysql, :postgresql].include? db
|
61
|
+
@@RES_TYPES.each do |res_type|
|
62
|
+
if db == :postgresql and res_type == :heavy
|
63
|
+
fetch_reserved_rds_instance_pricing(RDS_BASE_URL+"#{db}/pricing-#{res_type}-utilization-reserved-instances.min.js", res_type, db, false)
|
64
|
+
elsif db == :mysql
|
65
|
+
fetch_reserved_rds_instance_pricing(RDS_BASE_URL+"#{db}/pricing-#{res_type}-utilization-reserved-instances.min.js", res_type, db, false)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
else
|
69
|
+
@@RESERVED_DB_DEPLOY_TYPE[db].each {|db_type, db_instance|
|
70
|
+
@@RES_TYPES.each do |res_type|
|
71
|
+
db_instance.each do |dp_type|
|
72
|
+
is_byol = is_byol? dp_type
|
73
|
+
if db == :oracle
|
74
|
+
fetch_reserved_rds_instance_pricing(RDS_BASE_URL+"#{db}/pricing-#{dp_type}-#{res_type}-utilization-reserved-instances.min.js", res_type, db_type, is_byol)
|
75
|
+
elsif db == :sqlserver
|
76
|
+
fetch_reserved_rds_instance_pricing(RDS_BASE_URL+"#{db}/sqlserver-#{dp_type}-#{res_type}-ri.min.js", res_type, db_type, is_byol)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
}
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def fetch_on_demand_rds_instance_pricing(url, type_of_rds_instance, db_type, is_byol)
|
86
|
+
res = PriceList.fetch_url(url)
|
87
|
+
res['config']['regions'].each do |reg|
|
88
|
+
region_name = reg['region']
|
89
|
+
region = find_or_create_region(region_name)
|
90
|
+
reg['types'].each do |type|
|
91
|
+
type['tiers'].each do |tier|
|
92
|
+
begin
|
93
|
+
#
|
94
|
+
# this is special case URL, it is oracle - multiAZ type of deployment but it doesn't have mutliAZ attributes in json.
|
95
|
+
if url == "http://aws.amazon.com/rds/pricing/oracle/pricing-li-multiAZ-deployments.min.js"
|
96
|
+
is_multi_az = true
|
97
|
+
else
|
98
|
+
is_multi_az = is_multi_az? type["name"]
|
99
|
+
end
|
100
|
+
api_name, name = RdsInstanceType.get_name(type["name"], tier["name"], type_of_rds_instance != :ondemand)
|
101
|
+
|
102
|
+
instance_type = region.add_or_update_rds_instance_type(api_name, name)
|
103
|
+
instance_type.update_pricing(db_type, type_of_rds_instance, tier, is_multi_az, is_byol)
|
104
|
+
rescue UnknownTypeError
|
105
|
+
$stderr.puts "WARNING: encountered #{$!.message}"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def fetch_reserved_rds_instance_pricing(url, type_of_rds_instance, db_type, is_byol)
|
113
|
+
res = PriceList.fetch_url(url)
|
114
|
+
res['config']['regions'].each do |reg|
|
115
|
+
region_name = reg['region']
|
116
|
+
region = find_or_create_region(region_name)
|
117
|
+
reg['instanceTypes'].each do |type|
|
118
|
+
type['tiers'].each do |tier|
|
119
|
+
begin
|
120
|
+
is_multi_az = is_multi_az? type["type"]
|
121
|
+
api_name, name = RdsInstanceType.get_name(type["type"], tier["size"], true)
|
122
|
+
|
123
|
+
instance_type = region.add_or_update_rds_instance_type(api_name, name)
|
124
|
+
instance_type.update_pricing(db_type, type_of_rds_instance, tier, is_multi_az, is_byol)
|
125
|
+
rescue UnknownTypeError
|
126
|
+
$stderr.puts "WARNING: encountered #{$!.message}"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: amazon-pricing
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.41
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joe Kinsella
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-07-
|
11
|
+
date: 2014-07-22 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: A Ruby library for retrieving pricing for Amazon Web Services
|
14
14
|
email:
|
@@ -40,6 +40,10 @@ files:
|
|
40
40
|
- lib/amazon-pricing/rds-instance-type.rb
|
41
41
|
- lib/amazon-pricing/region.rb
|
42
42
|
- lib/amazon-pricing/version.rb
|
43
|
+
- lib/aws-price-list.rb
|
44
|
+
- lib/ec2-price-list.rb
|
45
|
+
- lib/gov-cloud-price-list.rb
|
46
|
+
- lib/rds-price-list.rb
|
43
47
|
- spec/instance_type_spec.rb
|
44
48
|
- spec/price_list_spec.rb
|
45
49
|
- spec/rds_pricing_spec.rb
|