awscosts 0.0.8 → 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -4
  3. data/awscosts.gemspec +1 -0
  4. data/fixtures/vcr_cassettes/AWSCosts_EBS.yml +17 -496
  5. data/fixtures/vcr_cassettes/AWSCosts_EC2OnDemand/EC2_type_of_linux.yml +63 -4583
  6. data/fixtures/vcr_cassettes/AWSCosts_EC2OnDemand/EC2_type_of_rhel.yml +63 -4585
  7. data/fixtures/vcr_cassettes/AWSCosts_EC2OnDemand/EC2_type_of_sles.yml +63 -4585
  8. data/fixtures/vcr_cassettes/AWSCosts_EC2OnDemand/EC2_type_of_windows.yml +63 -4583
  9. data/fixtures/vcr_cassettes/AWSCosts_EC2OnDemand/EC2_type_of_windows_with_sql.yml +63 -3535
  10. data/fixtures/vcr_cassettes/AWSCosts_EC2OnDemand/EC2_type_of_windows_with_sql_web.yml +63 -4060
  11. data/fixtures/vcr_cassettes/AWSCosts_EC2ReservedInstances/Reserved_instances/in_the_us-east-1_region/EC2_type_of_linux.yml +16 -5970
  12. data/fixtures/vcr_cassettes/AWSCosts_EC2ReservedInstances/Reserved_instances/in_the_us-east-1_region/EC2_type_of_rhel.yml +16 -5970
  13. data/fixtures/vcr_cassettes/AWSCosts_EC2ReservedInstances/Reserved_instances/in_the_us-east-1_region/EC2_type_of_sles.yml +16 -5970
  14. data/fixtures/vcr_cassettes/AWSCosts_EC2ReservedInstances/Reserved_instances/in_the_us-east-1_region/EC2_type_of_windows.yml +16 -5970
  15. data/fixtures/vcr_cassettes/AWSCosts_EC2ReservedInstances/Reserved_instances/in_the_us-east-1_region/EC2_type_of_windows_with_sql.yml +16 -5970
  16. data/fixtures/vcr_cassettes/AWSCosts_EC2ReservedInstances/Reserved_instances/in_the_us-east-1_region/EC2_type_of_windows_with_sql_web.yml +16 -5970
  17. data/fixtures/vcr_cassettes/AWSCosts_ELB.yml +17 -283
  18. data/fixtures/vcr_cassettes/AWSCosts_EMR.yml +12 -12
  19. data/fixtures/vcr_cassettes/AWSCosts_ElasticIPs.yml +17 -445
  20. data/fixtures/vcr_cassettes/AWSCosts_S3DataTransfer.yml +19 -925
  21. data/fixtures/vcr_cassettes/AWSCosts_S3Requests.yml +19 -454
  22. data/fixtures/vcr_cassettes/AWSCosts_S3Storage.yml +19 -1396
  23. data/lib/awscosts/cache.rb +26 -4
  24. data/lib/awscosts/ec2.rb +26 -5
  25. data/lib/awscosts/ec2_ebs.rb +3 -14
  26. data/lib/awscosts/ec2_ebs_optimized.rb +31 -0
  27. data/lib/awscosts/ec2_elastic_ips.rb +4 -4
  28. data/lib/awscosts/ec2_elb.rb +4 -4
  29. data/lib/awscosts/ec2_on_demand.rb +24 -19
  30. data/lib/awscosts/ec2_reserved_instances.rb +7 -8
  31. data/lib/awscosts/emr.rb +1 -2
  32. data/lib/awscosts/region.rb +12 -10
  33. data/lib/awscosts/s3.rb +4 -3
  34. data/lib/awscosts/s3_data_transfer.rb +7 -7
  35. data/lib/awscosts/s3_requests.rb +5 -6
  36. data/lib/awscosts/s3_storage.rb +7 -7
  37. data/lib/awscosts/version.rb +1 -1
  38. data/spec/awscosts/ec2_ebs_spec.rb +17 -33
  39. data/spec/awscosts/ec2_elastic_ips_spec.rb +2 -0
  40. data/spec/awscosts/ec2_reserved_instances_spec.rb +3 -0
  41. metadata +17 -2
@@ -1,15 +1,28 @@
1
1
  require 'httparty'
2
- require 'json'
2
+ require 'oj'
3
3
 
4
4
  module AWSCosts::Cache
5
5
  extend self
6
6
 
7
7
  BASE_URI = "http://aws.amazon.com"
8
+ BASE_JSONP_URI = "http://a0.awsstatic.com"
8
9
 
9
10
  def get uri, base_uri = BASE_URI, &block
10
- cache[uri] ||= begin
11
- yield JSON.parse(HTTParty.get("#{base_uri}#{uri}").body)
12
- end
11
+ cache["#{base_uri}#{uri}"] ||= Oj::load(HTTParty.get("#{base_uri}#{uri}").body)
12
+ yield cache["#{base_uri}#{uri}"]
13
+ end
14
+
15
+ def get_jsonp uri, base_uri = BASE_JSONP_URI, &block
16
+ attempts = 0
17
+ cache["#{base_uri}#{uri}"] ||= begin
18
+ extract_json_from_callback(HTTParty.get("#{base_uri}#{uri}").body)
19
+ rescue NoMethodError
20
+ attempts += 1
21
+ retry if attempts < 5
22
+ raise "Failed to retrieve or parse data for #{base_uri}#{uri}"
23
+ end
24
+
25
+ yield cache["#{base_uri}#{uri}"]
13
26
  end
14
27
 
15
28
  private
@@ -17,6 +30,15 @@ module AWSCosts::Cache
17
30
  @cache ||= {}
18
31
  end
19
32
 
33
+ def extract_json_from_callback body
34
+ body.match /^.*callback\((\{.*\})\);$/
35
+ body = $1
36
+
37
+ # Handle "json" with keys that are not quoted
38
+ # When we get {foo: "1"} instead of {"foo": "1"}
39
+ # http://stackoverflow.com/questions/2060356/parsing-json-without-quoted-keys
40
+ Oj::load(body.gsub(/(\w+)\s*:/, '"\1":'))
41
+ end
20
42
  end
21
43
 
22
44
 
@@ -2,6 +2,7 @@ require 'awscosts/ec2_on_demand'
2
2
  require 'awscosts/ec2_reserved_instances'
3
3
  require 'awscosts/ec2_elb'
4
4
  require 'awscosts/ec2_ebs'
5
+ require 'awscosts/ec2_ebs_optimized'
5
6
  require 'awscosts/ec2_elastic_ips'
6
7
 
7
8
  class AWSCosts::EC2
@@ -11,28 +12,48 @@ class AWSCosts::EC2
11
12
  TYPES = { windows: 'mswin', linux: 'linux', windows_with_sql: 'mswinSQL',
12
13
  windows_with_sql_web: 'mswinSQLWeb', rhel: 'rhel', sles: 'sles' }
13
14
 
15
+ REGION_MAPPING = {
16
+ 'us-east-1' => "us-east",
17
+ 'us-west-1' => "us-west",
18
+ 'us-west-2' => "us-west-2",
19
+ 'eu-west-1' => "eu-ireland",
20
+ 'eu-central-1' => "eu-central-1",
21
+ 'ap-southeast-1' => "apac-sin",
22
+ 'ap-southeast-2' =>"apac-syd",
23
+ 'ap-northeast-1' =>"apac-tokyo",
24
+ 'sa-east-1' => "sa-east-1"
25
+ }
26
+
14
27
  def initialize region
15
28
  @region = region
16
29
  end
17
30
 
18
31
  def on_demand(type)
32
+ raise ArgumentError.new("Unknown platform: #{type}") if TYPES[type].nil?
19
33
  AWSCosts::EC2OnDemand.fetch(TYPES[type], self.region.name)
20
34
  end
21
35
 
22
- def reserved(type, utilisation= :light)
23
- AWSCosts::EC2ReservedInstances.fetch(TYPES[type], utilisation, self.region.name)
36
+ def reserved(type, utilisation = :light)
37
+ r = self.region.name
38
+ r = 'us-east' if r == 'us-east-1'
39
+ raise ArgumentError.new("Unknown platform: #{type}") if TYPES[type].nil?
40
+ AWSCosts::EC2ReservedInstances.fetch(TYPES[type], utilisation, r)
24
41
  end
25
42
 
26
43
  def elb
27
- AWSCosts::ELB.fetch(self.region.price_mapping)
44
+ AWSCosts::ELB.fetch(REGION_MAPPING[self.region.name])
28
45
  end
29
46
 
30
47
  def ebs
31
- AWSCosts::EBS.fetch(self.region.price_mapping)
48
+ AWSCosts::EBS.fetch(REGION_MAPPING[self.region.name])
49
+ end
50
+
51
+ def ebs_optimized
52
+ AWSCosts::EBSOptimized.fetch(REGION_MAPPING[self.region.name])
32
53
  end
33
54
 
34
55
  def elastic_ips
35
- AWSCosts::ElasticIPs.fetch(self.region.price_mapping)
56
+ AWSCosts::ElasticIPs.fetch(REGION_MAPPING[self.region.name])
36
57
  end
37
58
  end
38
59
 
@@ -1,11 +1,7 @@
1
1
  require 'httparty'
2
- require 'json'
3
2
 
4
3
  class AWSCosts::EBS
5
4
 
6
- TYPES = { 'ebsVols' => :standard, 'ebsPIOPSVols' => :provisioned_iops,
7
- 'ebsSnapsToS3' => :snapshots_to_s3 }
8
-
9
5
  def initialize data
10
6
  @data= data
11
7
  end
@@ -15,17 +11,10 @@ class AWSCosts::EBS
15
11
  end
16
12
 
17
13
  def self.fetch region
18
- transformed= AWSCosts::Cache.get('/ec2/pricing/pricing-ebs.json') do |data|
14
+ transformed = AWSCosts::Cache.get_jsonp('/pricing/1/ebs/pricing-ebs.min.js') do |data|
19
15
  result = {}
20
- data['config']['regions'].each do |region|
21
- container = {}
22
- region['types'].each do |type|
23
- container[TYPES[type['name']]] = {}
24
- type['values'].each do |value|
25
- container[TYPES[type['name']]][value['rate']] = value['prices']['USD'].to_f
26
- end
27
- end
28
- result[region['region']] = container
16
+ data['config']['regions'].each do |r|
17
+ result[r['region']] = r['types']
29
18
  end
30
19
  result
31
20
  end
@@ -0,0 +1,31 @@
1
+ require 'httparty'
2
+
3
+ class AWSCosts::EBSOptimized
4
+
5
+ def initialize data
6
+ @data = data
7
+ end
8
+
9
+ def price type = nil
10
+ type.nil? ? @data : @data[type]
11
+ end
12
+
13
+ def self.fetch region
14
+ transformed = AWSCosts::Cache.get_jsonp('/pricing/1/ec2/pricing-ebs-optimized-instances.min.js') do |data|
15
+ result = {}
16
+ data['config']['regions'].each do |r|
17
+ container = {}
18
+ r['instanceTypes'].each do |type|
19
+ type['sizes'].each do |size|
20
+ container[size['size']] = size['valueColumns'].select{|v| v['name'] == 'ebsOptimized'}.first['prices']['USD'].to_f
21
+ end
22
+ end
23
+ result[r['region']] = container
24
+ end
25
+ result
26
+ end
27
+ self.new(transformed[region])
28
+ end
29
+
30
+ end
31
+
@@ -25,16 +25,16 @@ class AWSCosts::ElasticIPs
25
25
  end
26
26
 
27
27
  def self.fetch region
28
- transformed= AWSCosts::Cache.get('/ec2/pricing/pricing-elastic-ips.json') do |data|
28
+ transformed = AWSCosts::Cache.get_jsonp('/pricing/1/ec2/pricing-elastic-ips.min.js') do |data|
29
29
  result = {}
30
- data['config']['regions'].each do |region|
30
+ data['config']['regions'].each do |r|
31
31
  container = {}
32
- region['types'].each do |type|
32
+ r['types'].each do |type|
33
33
  type['values'].each do |value|
34
34
  container[value['rate']] = value['prices']['USD'].to_f
35
35
  end
36
36
  end
37
- result[region['region']] = container
37
+ result[r['region']] = container
38
38
  end
39
39
  result
40
40
  end
@@ -13,16 +13,16 @@ class AWSCosts::ELB
13
13
  end
14
14
 
15
15
  def self.fetch region
16
- transformed= AWSCosts::Cache.get('/ec2/pricing/pricing-elb.json') do |data|
16
+ transformed = AWSCosts::Cache.get_jsonp('/pricing/1/ec2/pricing-elb.min.js') do |data|
17
17
  result = {}
18
- data['config']['regions'].each do |region|
18
+ data['config']['regions'].each do |r|
19
19
  container = {}
20
- region['types'].each do |type|
20
+ r['types'].each do |type|
21
21
  type['values'].each do |value|
22
22
  container[value['rate']] = value['prices']['USD'].to_f
23
23
  end
24
24
  end
25
- result[region['region']] = container
25
+ result[r['region']] = container
26
26
  end
27
27
  result
28
28
  end
@@ -22,7 +22,7 @@ class AWSCosts::EC2OnDemand
22
22
  'hiIoODI.xxxxl' => 'hi1.4xlarge' }
23
23
 
24
24
  def initialize data
25
- @data= data
25
+ @data = data
26
26
  end
27
27
 
28
28
  def price size=nil
@@ -30,30 +30,35 @@ class AWSCosts::EC2OnDemand
30
30
  end
31
31
 
32
32
  def self.fetch type, region
33
- transformed= AWSCosts::Cache.get("/pricing/1/deprecated/ec2/#{type}-od.json", 'https://a0.awsstatic.com') do |data|
34
- result = {}
35
- data['config']['regions'].each do |region|
36
- platforms = {}
37
- region['instanceTypes'].each do |instance_type|
38
- instance_type['sizes'].each do |instance_size|
39
- size = instance_size['size']
40
- platform_cost = Hash.new({})
41
- instance_size['valueColumns'].each do |value|
42
- platform_cost[value['name']] = value['prices']['USD'].to_f
43
- end
33
+ result = {}
34
+ ['/pricing/1/ec2/%s-od.min.js', '/pricing/1/ec2/previous-generation/%s-od.min.js'].each do |uri|
35
+ AWSCosts::Cache.get_jsonp(uri % type) do |data|
36
+ data['config']['regions'].each do |r|
37
+ result[r['region']] ||= {}
38
+ platforms = result[r['region']]
39
+ r['instanceTypes'].each do |instance_type|
40
+ instance_type['sizes'].each do |instance_size|
41
+ size = instance_size['size']
42
+ platform_cost = Hash.new({})
43
+ instance_size['valueColumns'].each do |value|
44
+ # Don't return 0.0 for "N/A" since that is misleading
45
+ platform_cost[value['name']] = value['prices']['USD'] == 'N/A' ? nil : value['prices']['USD'].to_f
46
+ end
44
47
 
45
- platform_cost.each_pair do |p,v|
46
- platforms[p] = {} unless platforms.key?(p)
47
- platforms[p][size] = v
48
+ platform_cost.each_pair do |p,v|
49
+ platforms[p] = {} unless platforms.key?(p)
50
+ platforms[p][size] = v
51
+ end
48
52
  end
49
53
  end
50
54
  end
51
- result[region['region']] = platforms
52
55
  end
53
- result
54
56
  end
55
- #type == 'sles' ? self.new(transformed[region]['linux']) :
56
- self.new(transformed[region][type])
57
+
58
+ raise "No result for region #{region} while fetching EC2 OnDemand Pricing" if result[region].nil?
59
+ raise "No result for #{type} in region #{region} while fetching EC2 OnDemand Pricing" if result[region][type].nil?
60
+
61
+ self.new(result[region][type])
57
62
  end
58
63
 
59
64
  end
@@ -1,5 +1,4 @@
1
1
  require 'httparty'
2
- require 'json'
3
2
  class AWSCosts::EC2ReservedInstances
4
3
 
5
4
  TERMS = { one_year: 'yrTerm1', three_year: 'yrTerm3' }
@@ -42,19 +41,19 @@ class AWSCosts::EC2ReservedInstances
42
41
  end
43
42
  end
44
43
 
45
-
46
44
  def self.fetch type, utilisation, region
47
- transformed= AWSCosts::Cache.get("/pricing/1/deprecated/ec2/#{type}-ri-#{utilisation}.json", 'https://a0.awsstatic.com') do |data|
45
+ transformed = AWSCosts::Cache.get_jsonp("/pricing/1/ec2/#{type}-ri-#{utilisation}.min.js") do |data|
48
46
  result = {}
49
- data['config']['regions'].each do |region|
47
+ data['config']['regions'].each do |r|
50
48
  platforms = {}
51
- region['instanceTypes'].each do |instance_type|
49
+ r['instanceTypes'].each do |instance_type|
52
50
  instance_type['sizes'].each do |instance_size|
53
51
  size = instance_size['size']
54
52
  platform_cost = Hash.new({})
55
53
 
56
54
  instance_size['valueColumns'].each do |value|
57
- platform_cost[value['name']] = value['prices']['USD']
55
+ # Don't return 0.0 for "N/A" since that is misleading
56
+ platform_cost[value['name']] = value['prices']['USD'] == 'N/A' ? nil : value['prices']['USD'].to_f
58
57
  end
59
58
 
60
59
  platform_cost.each_pair do |p,v|
@@ -63,11 +62,11 @@ class AWSCosts::EC2ReservedInstances
63
62
  end
64
63
  end
65
64
  end
66
- result[region['region']] = platforms
65
+ result[r['region']] = platforms
67
66
  end
68
67
  result
69
68
  end
70
- region == 'us-east-1' ? self.new(transformed['us-east']) : self.new(transformed[region])
69
+ self.new(transformed[region])
71
70
  end
72
71
 
73
72
  end
@@ -1,5 +1,4 @@
1
1
  require 'httparty'
2
- require 'json'
3
2
 
4
3
  class AWSCosts::EMR
5
4
 
@@ -45,7 +44,7 @@ class AWSCosts::EMR
45
44
  end
46
45
  platform_cost.each_pair do |p,v|
47
46
  platforms[p] = {} unless platforms.key?(p)
48
- platforms[p][TYPE_TRANSLATION["#{type}.#{size}"]] = v
47
+ platforms[p][size] = v
49
48
  end
50
49
  end
51
50
  end
@@ -1,17 +1,18 @@
1
1
 
2
2
  class AWSCosts::Region
3
3
 
4
- attr_reader :name, :full_name, :price_mapping
4
+ attr_reader :name, :full_name, :price_mapping, :emr_mapping
5
5
 
6
6
  SUPPORTED = {
7
- 'us-east-1' => { :full_name => 'US (Northern Virginia)', :price_mapping => 'us-east' },
8
- 'us-west-1' => { :full_name => 'US (Northern California)', :price_mapping => 'us-west' },
9
- 'us-west-2' => { :full_name => 'US (Oregon)', :price_mapping => 'us-west-2' },
10
- 'eu-west-1' => { :full_name => 'EU (Ireland)', :price_mapping => 'eu-ireland' },
11
- 'ap-southeast-1' => { :full_name => 'Asia Pacific (Singapore)', :price_mapping => 'apac-sin' },
12
- 'ap-southeast-2' => { :full_name => 'Asia Pacific (Sydney)', :price_mapping => 'apac-syd' },
13
- 'ap-northeast-1' => { :full_name => 'Asia Pacific (Tokyo)', :price_mapping => 'apac-tokyo' },
14
- 'sa-east-1' => { :full_name => 'South America (Sao Paulo)', :price_mapping => 'sa-east-1' }
7
+ 'us-east-1' => { :full_name => 'US (Northern Virginia)', :price_mapping => 'us-east-1', :emr_mapping => 'us-east' },
8
+ 'us-west-1' => { :full_name => 'US (Northern California)', :price_mapping => 'us-west-1', :emr_mapping => 'us-west' },
9
+ 'us-west-2' => { :full_name => 'US (Oregon)', :price_mapping => 'us-west-2', :emr_mapping => 'us-west-2' },
10
+ 'eu-west-1' => { :full_name => 'EU (Ireland)', :price_mapping => 'eu-west-1', :emr_mapping => 'eu-ireland' },
11
+ 'eu-central-1' => { :full_name => 'EU (Frankfurt)' },
12
+ 'ap-southeast-1' => { :full_name => 'Asia Pacific (Singapore)', :price_mapping => 'ap-southeast-1', :emr_mapping => 'apac-sin' },
13
+ 'ap-southeast-2' => { :full_name => 'Asia Pacific (Sydney)', :price_mapping => 'ap-southeast-2', :emr_mapping => 'apac-syd' },
14
+ 'ap-northeast-1' => { :full_name => 'Asia Pacific (Tokyo)', :price_mapping => 'ap-northeast-1', :emr_mapping => 'apac-tokyo' },
15
+ 'sa-east-1' => { :full_name => 'South America (Sao Paulo)', :price_mapping => 'sa-east-1', :emr_mapping => 'sa-east-1' }
15
16
  }
16
17
 
17
18
  def self.find name
@@ -24,7 +25,7 @@ class AWSCosts::Region
24
25
  end
25
26
 
26
27
  def emr
27
- AWSCosts::EMR.fetch(self.price_mapping)
28
+ AWSCosts::EMR.fetch(self.emr_mapping)
28
29
  end
29
30
 
30
31
  def s3
@@ -36,6 +37,7 @@ class AWSCosts::Region
36
37
  @name = name
37
38
  @full_name = SUPPORTED[name][:full_name]
38
39
  @price_mapping = SUPPORTED[name][:price_mapping]
40
+ @emr_mapping = SUPPORTED[name][:emr_mapping]
39
41
  end
40
42
 
41
43
  end
@@ -11,6 +11,7 @@ class AWSCosts::S3
11
11
  'us-west-1' => "us-west-1",
12
12
  'us-west-2' => "us-west-2",
13
13
  'eu-west-1' => "eu-west-1",
14
+ 'eu-central-1' => "eu-central-1",
14
15
  'ap-southeast-1' => "ap-southeast-1",
15
16
  'ap-southeast-2' =>"ap-southeast-2",
16
17
  'ap-northeast-1' =>"ap-northeast-1",
@@ -22,15 +23,15 @@ class AWSCosts::S3
22
23
  end
23
24
 
24
25
  def storage
25
- AWSCosts::S3Storage.fetch(self.region)
26
+ AWSCosts::S3Storage.fetch(@region)
26
27
  end
27
28
 
28
29
  def data_transfer
29
- AWSCosts::S3DataTransfer.fetch(self.region)
30
+ AWSCosts::S3DataTransfer.fetch(@region)
30
31
  end
31
32
 
32
33
  def requests
33
- AWSCosts::S3Requests.fetch(self.region)
34
+ AWSCosts::S3Requests.fetch(@region)
34
35
  end
35
36
  end
36
37
 
@@ -1,12 +1,11 @@
1
1
  require 'httparty'
2
- require 'json'
3
2
 
4
3
  class AWSCosts::S3DataTransfer
5
4
 
6
5
  TYPES = %w{dataXferInS3, dataXferOutS3CrossRegion, dataXferOutS3}
7
6
 
8
7
  def initialize data
9
- @data= data
8
+ @data = data
10
9
  end
11
10
 
12
11
  def price type = nil
@@ -14,17 +13,18 @@ class AWSCosts::S3DataTransfer
14
13
  end
15
14
 
16
15
  def self.fetch region
17
- transformed= AWSCosts::Cache.get("/s3/pricing/pricing-data-transfer.json") do |data|
16
+ transformed = AWSCosts::Cache.get_jsonp("/pricing/1/s3/pricing-data-transfer-s3.min.js") do |data|
18
17
  result = {}
19
- data['config']['regions'].each do |region|
18
+ data['config']['regions'].each do |r|
20
19
  types = {}
21
- region['types'].each do |type|
20
+ r['types'].each do |type|
22
21
  types[type['name']] = {}
23
22
  type['tiers'].each do |tier|
24
- types[type['name']][tier['name']] = tier['prices']['USD'].to_f
23
+ # Don't return 0.0 for "contactus" since that is misleading
24
+ types[type['name']][tier['name']] = tier['prices']['USD'] == 'contactus' ? nil : tier['prices']['USD'].to_f
25
25
  end
26
26
  end
27
- result[region['region']] = types
27
+ result[r['region']] = types
28
28
  end
29
29
  result
30
30
  end