amazon-pricing 0.1.40 → 0.1.41
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.
- 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
|