awscosts 0.0.8 → 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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