awscosts 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in awscosts.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Stephen Bartlett
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,86 @@
1
+ # Awscosts
2
+
3
+ AWSCosts allows programmatic access to AWS pricing.
4
+
5
+ Useful for calculating the running costs of your fleet of EC2 instances and
6
+ associated services such as ELBs, RDS, CloudWatch, etc.
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ gem 'awscosts'
13
+
14
+ Or install it yourself as:
15
+
16
+ $ gem install awscosts
17
+
18
+ ## Usage
19
+
20
+ Pricing is usually defined by region
21
+
22
+ region = AWSCosts.region 'us-east-1'
23
+
24
+ ### On Demand
25
+
26
+ To find the price of all on-demand EC2 instances running linux
27
+
28
+ region.ec2.on_demand(:linux).price
29
+
30
+ You can pass an argument to price to filter by the instance type.
31
+
32
+ region.ec2.on_demand(:linux).price('m1.medium')
33
+
34
+ ### Reserved
35
+
36
+ To find the **upfront** price of reserving Windows instance for light utilization
37
+
38
+ region.ec2.reserved(:windows, :light).upfront
39
+
40
+ To find the **hourly** price of a `m1.large` reserved Windows with SQL instance for medium utilization
41
+
42
+ region.ec2.reserved(:windows_with_sql, :medium).hourly('m1.large')
43
+
44
+ ### ELB
45
+
46
+ To find the hourly price of an ELB in the Sydney region.
47
+
48
+ AWSCosts.region('ap-southeast-2').ec2.elb.price_per_hour
49
+
50
+ To find the price per GB processed of an ELB in the Sydney region.
51
+
52
+ AWSCosts.region('ap-southeast-2').ec2.elb.price_per_gb
53
+
54
+ ## Supported Services
55
+
56
+ The following AWS services are currently support (more to come):
57
+
58
+ * On-demand instances
59
+ * Reserved light utilisation
60
+ * Reserved medium utilisation
61
+ * Reserved heavy utilisation
62
+ * Elastic Load Balancer (ELB)
63
+
64
+ EC2 platforms:
65
+
66
+ * Linux (:linux)
67
+ * RHEL (:rhel)
68
+ * SLES (:sles)
69
+ * Windows (:windows)
70
+ * Windows with SQL (:windows_with_sql)
71
+ * Windows with SQL Web (:windows_with_sql_web)
72
+
73
+ ## Roadmap
74
+
75
+ Possible items on the roadmap are:
76
+
77
+ 1. Cost calculation using a DSL to describe your AWS environment.
78
+ 2. Cost calculation from a Cloudformation template.
79
+
80
+ ## Contributing
81
+
82
+ 1. Fork it
83
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
84
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
85
+ 4. Push to the branch (`git push origin my-new-feature`)
86
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ desc "Run the tests"
5
+ RSpec::Core::RakeTask.new(:spec) do |t|
6
+ t.pattern = ENV['rspec_pattern'] || "spec/**/*_spec.rb"
7
+ t.rspec_opts = [
8
+ "--colour",
9
+ "--require", "spec_helper"
10
+ ]
11
+ end
data/awscosts.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'awscosts/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "awscosts"
8
+ spec.version = AWSCosts::VERSION
9
+ spec.authors = ["Stephen Bartlett"]
10
+ spec.email = ["stephenb@rtlett.org"]
11
+ spec.description = %q{AWSCosts provides an easier way to calculate the costs of running your project in AWS}
12
+ spec.summary = %q{Programmatic access to AWS pricing}
13
+ spec.homepage = "https://github.com/srbartlett/awscosts"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "httparty", "~> 0.11.0"
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.3"
24
+ spec.add_development_dependency "rake"
25
+ spec.add_development_dependency 'rspec'
26
+ end
@@ -0,0 +1,29 @@
1
+ require 'awscosts/ec2_on_demand'
2
+ require 'awscosts/ec2_reserved_instances'
3
+ require 'awscosts/ec2_elb'
4
+
5
+ class AWSCosts::EC2
6
+
7
+ attr_reader :region
8
+
9
+ TYPES = { windows: 'mswin', linux: 'linux', windows_with_sql: 'mswinSQL',
10
+ windows_with_sql_web: 'mswinSQLWeb', rhel: 'rhel', sles: 'sles' }
11
+
12
+ def initialize region
13
+ @region = region
14
+ end
15
+
16
+ def on_demand(type)
17
+ AWSCosts::EC2OnDemand.fetch(type, self.region.price_mapping)
18
+ end
19
+
20
+ def reserved(type, utilisation= :light)
21
+ AWSCosts::EC2ReservedInstances.fetch(type, utilisation, self.region.price_mapping)
22
+ end
23
+
24
+ def elb
25
+ AWSCosts::ELB.fetch(self.region.price_mapping)
26
+ end
27
+ end
28
+
29
+
@@ -0,0 +1,36 @@
1
+ require 'httparty'
2
+ require 'json'
3
+
4
+ class AWSCosts::ELB
5
+
6
+ def initialize data
7
+ @data= data
8
+ end
9
+
10
+ def price_per_hour
11
+ @data['perELBHour']
12
+ end
13
+
14
+ def price_per_gb
15
+ @data['perGBProcessed']
16
+ end
17
+
18
+ def self.fetch region
19
+ @fetch ||= begin
20
+ result = {}
21
+ data= JSON.parse(HTTParty.get('http://aws.amazon.com/ec2/pricing/pricing-elb.json').body)
22
+ data['config']['regions'].each do |region|
23
+ container = {}
24
+ region['types'].each do |type|
25
+ type['values'].each do |value|
26
+ container[value['rate']] = value['prices']['USD'].to_f
27
+ end
28
+ end
29
+ result[region['region']] = container
30
+ end
31
+ result
32
+ end
33
+ self.new(@fetch[region])
34
+ end
35
+
36
+ end
@@ -0,0 +1,67 @@
1
+ require 'httparty'
2
+ require 'json'
3
+
4
+ class AWSCosts::EC2OnDemand
5
+
6
+ TYPE_TRANSLATION = { 'stdODI.sm' => 'm1.small',
7
+ 'stdODI.med' => 'm1.medium',
8
+ 'stdODI.lg' => 'm1.large',
9
+ 'stdODI.xl' => 'm1.xlarge',
10
+ 'secgenstdODI.xl' => 'm3.xlarge',
11
+ 'secgenstdODI.xxl' => 'm3.2xlarge',
12
+ 'uODI.u' => 't1.micro',
13
+ 'hiMemODI.xl' => 'm2.xlarge',
14
+ 'hiMemODI.xxl' => 'm2.2xlarge',
15
+ 'hiMemODI.xxxxl' => 'm2.4xlarge',
16
+ 'hiCPUODI.med' => 'c1.medium',
17
+ 'hiCPUODI.xl' => 'c1.xlarge',
18
+ 'clusterComputeI.xxxxl' => 'cc1.4xlarge',
19
+ 'clusterComputeI.xxxxxxxxl' => 'cc2.8xlarge',
20
+ 'clusterGPUI.xxxxl' => 'cg1.4xlarge',
21
+ 'clusterHiMemODI.xxxxxxxxl' => 'cr1.8xlarge',
22
+ 'hiStoreODI.xxxxxxxxl' => 'hs1.8xlarge',
23
+ 'hiIoODI.xxxxl' => 'hi1.4xlarge' }
24
+
25
+ def initialize type, data
26
+ @type = type
27
+ @data= data
28
+ end
29
+
30
+ def price size=nil
31
+ size ? @data[TYPE_MAPPING[@type]][size] : @data[TYPE_MAPPING[@type]]
32
+ end
33
+
34
+ def self.fetch type, region
35
+ raw_data= cache[type] ||= begin
36
+ result = {}
37
+ data= JSON.parse(HTTParty.get("http://aws.amazon.com/ec2/pricing/json/#{AWSCosts::EC2::TYPE_MAPPING[type]}-od.json").body)
38
+ data['config']['regions'].each do |region|
39
+ platforms = {}
40
+ region['instanceTypes'].each do |instance_type|
41
+ type = instance_type['type']
42
+ instance_type['sizes'].each do |instance_size|
43
+ size = instance_size['size']
44
+ platform_cost = Hash.new({})
45
+ instance_size['valueColumns'].each do |value|
46
+ platform_cost[value['name']] = value['prices']['USD'].to_f
47
+ end
48
+
49
+ platform_cost.each_pair do |p,v|
50
+ platforms[p] = {} unless platforms.key?(p)
51
+ platforms[p][TYPE_TRANSLATION["#{type}.#{size}"]] = v
52
+ end
53
+ end
54
+ end
55
+ result[region['region']] = platforms
56
+ end
57
+ result
58
+ end
59
+ self.new(type, raw_data[region])
60
+ end
61
+
62
+ private
63
+ def self.cache
64
+ @@cache ||= {}
65
+ end
66
+ end
67
+
@@ -0,0 +1,81 @@
1
+ require 'httparty'
2
+ require 'json'
3
+ class AWSCosts::EC2ReservedInstances
4
+
5
+ TERMS = { one_year: 'yrTerm1', three_year: 'yrTerm3' }
6
+
7
+ TYPE_TRANSLATION = { 'stdResI.sm' => 'm1.small',
8
+ 'stdResI.med' => 'm1.medium',
9
+ 'stdResI.lg' => 'm1.large',
10
+ 'stdResI.xl' => 'm1.xlarge',
11
+ 'secgenstdResI.xl' => 'm3.xlarge',
12
+ 'secgenstdResI.xxl' => 'm3.2xlarge',
13
+ 'uResI.u' => 't1.micro',
14
+ 'hiMemResI.xl' => 'm2.xlarge',
15
+ 'hiMemResI.xxl' => 'm2.2xlarge',
16
+ 'hiMemResI.xxxxl' => 'm2.4xlarge',
17
+ 'hiCPUResI.med' => 'c1.medium',
18
+ 'hiCPUResI.xl' => 'c1.xlarge',
19
+ 'clusterComputeI.xxxxl' => 'cc1.4xlarge',
20
+ 'clusterComputeI.xxxxxxxxl' => 'cc2.8xlarge',
21
+ 'clusterGPUI.xxxxl' => 'cg1.4xlarge',
22
+ 'clusterHiMemResI.xxxxxxxxl' => 'cr1.8xlarge',
23
+ 'hiStoreResI.xxxxxxxxl' => 'hs1.8xlarge',
24
+ 'hiIoResI.xxxxl' => 'hi1.4xlarge' }
25
+ def initialize data
26
+ @data= data
27
+ end
28
+
29
+ def upfront term, size=nil
30
+ if size
31
+ @data[TERMS[term]][size]
32
+ else
33
+ @data[TERMS[term]]
34
+ end
35
+ end
36
+
37
+ def hourly term, size=nil
38
+ if size
39
+ @data["#{TERMS[term]}Hourly"][size]
40
+ else
41
+ @data["#{TERMS[term]}Hourly"][size]
42
+ end
43
+ end
44
+
45
+
46
+ def self.fetch type, utilisation, region
47
+ raw_data= cache[type] ||= begin
48
+ result = {}
49
+ data= JSON.parse(HTTParty.get("http://aws.amazon.com/ec2/pricing/json/#{AWSCosts::EC2::TYPES[type]}-ri-#{utilisation}.json").body)
50
+ data['config']['regions'].each do |region|
51
+ platforms = {}
52
+ region['instanceTypes'].each do |instance_type|
53
+ type = instance_type['type']
54
+ instance_type['sizes'].each do |instance_size|
55
+ size = instance_size['size']
56
+ platform_cost = Hash.new({})
57
+
58
+ instance_size['valueColumns'].each do |value|
59
+ platform_cost[value['name']] = value['prices']['USD']
60
+ end
61
+
62
+ platform_cost.each_pair do |p,v|
63
+ platforms[p] = {} unless platforms.key?(p)
64
+ platforms[p][TYPE_TRANSLATION["#{type}.#{size}"]] = v
65
+ end
66
+ end
67
+ end
68
+ result[region['region']] = platforms
69
+ end
70
+ result
71
+ end
72
+ self.new(raw_data[region])
73
+ end
74
+
75
+
76
+ private
77
+ def self.cache
78
+ @@cache ||= {}
79
+ end
80
+ end
81
+
@@ -0,0 +1,34 @@
1
+
2
+ class AWSCosts::Region
3
+
4
+ attr_reader :name, :full_name, :price_mapping
5
+
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' }
15
+ }
16
+
17
+ def self.find name
18
+ SUPPORTED[name] ? self.new(name) : nil
19
+ end
20
+
21
+
22
+ def ec2
23
+ AWSCosts::EC2.new(self)
24
+ end
25
+
26
+
27
+ private
28
+ def initialize name
29
+ @name = name
30
+ @full_name = SUPPORTED[name][:full_name]
31
+ @price_mapping = SUPPORTED[name][:price_mapping]
32
+ end
33
+
34
+ end
@@ -0,0 +1,3 @@
1
+ module AWSCosts
2
+ VERSION = "0.0.1"
3
+ end
data/lib/awscosts.rb ADDED
@@ -0,0 +1,13 @@
1
+ require "awscosts/version"
2
+ require "awscosts/region"
3
+ require "awscosts/ec2"
4
+
5
+ module AWSCosts
6
+
7
+ extend self
8
+
9
+ def region name
10
+ AWSCosts::Region.find name
11
+ end
12
+
13
+ end
@@ -0,0 +1,12 @@
1
+ require 'spec_helper'
2
+
3
+ describe AWSCosts::EC2 do
4
+
5
+ subject { AWSCosts::EC2.new('us-east-1') }
6
+
7
+ it { should respond_to(:region) }
8
+
9
+
10
+ end
11
+
12
+
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ describe AWSCosts::Region do
4
+
5
+ subject { AWSCosts::Region.find('us-east-1') }
6
+
7
+ it { should respond_to(:name) }
8
+ it { should respond_to(:full_name) }
9
+
10
+ describe '.find' do
11
+
12
+ describe 'for a supported region' do
13
+ it 'should be an instance of Region' do
14
+ subject.should be_a(AWSCosts::Region)
15
+ end
16
+ end
17
+ describe 'for an unsupported region' do
18
+ it 'should return nothing' do
19
+ AWSCosts::Region.find('NotValid').should be_nil
20
+ end
21
+ end
22
+ end
23
+
24
+ describe '.ec2' do
25
+ it 'should be an instance of EC2' do
26
+ subject.ec2.should be_a(AWSCosts::EC2)
27
+ end
28
+
29
+ end
30
+ end
31
+
32
+
@@ -0,0 +1,5 @@
1
+ require 'awscosts'
2
+
3
+
4
+
5
+
metadata ADDED
@@ -0,0 +1,136 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: awscosts
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Stephen Bartlett
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-05-24 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: httparty
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 0.11.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 0.11.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: bundler
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '1.3'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '1.3'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rake
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rspec
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ description: AWSCosts provides an easier way to calculate the costs of running your
79
+ project in AWS
80
+ email:
81
+ - stephenb@rtlett.org
82
+ executables: []
83
+ extensions: []
84
+ extra_rdoc_files: []
85
+ files:
86
+ - .gitignore
87
+ - Gemfile
88
+ - LICENSE.txt
89
+ - README.md
90
+ - Rakefile
91
+ - awscosts.gemspec
92
+ - lib/awscosts.rb
93
+ - lib/awscosts/ec2.rb
94
+ - lib/awscosts/ec2_elb.rb
95
+ - lib/awscosts/ec2_on_demand.rb
96
+ - lib/awscosts/ec2_reserved_instances.rb
97
+ - lib/awscosts/region.rb
98
+ - lib/awscosts/version.rb
99
+ - spec/awscosts/ec2_spec.rb
100
+ - spec/awscosts/region_spec.rb
101
+ - spec/spec_helper.rb
102
+ homepage: https://github.com/srbartlett/awscosts
103
+ licenses:
104
+ - MIT
105
+ post_install_message:
106
+ rdoc_options: []
107
+ require_paths:
108
+ - lib
109
+ required_ruby_version: !ruby/object:Gem::Requirement
110
+ none: false
111
+ requirements:
112
+ - - ! '>='
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ segments:
116
+ - 0
117
+ hash: 4440165124257606433
118
+ required_rubygems_version: !ruby/object:Gem::Requirement
119
+ none: false
120
+ requirements:
121
+ - - ! '>='
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ segments:
125
+ - 0
126
+ hash: 4440165124257606433
127
+ requirements: []
128
+ rubyforge_project:
129
+ rubygems_version: 1.8.23
130
+ signing_key:
131
+ specification_version: 3
132
+ summary: Programmatic access to AWS pricing
133
+ test_files:
134
+ - spec/awscosts/ec2_spec.rb
135
+ - spec/awscosts/region_spec.rb
136
+ - spec/spec_helper.rb