joyent-cloud-pricing 1.0.0
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 +7 -0
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/.travis.yml +11 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +71 -0
- data/Rakefile +2 -0
- data/bin/joyent-price-helper +8 -0
- data/config/commit_pricing.yml.example +14 -0
- data/config/joyent_pricing.yml +47 -0
- data/joyent-cloud-pricing.gemspec +30 -0
- data/lib/pricing.rb +24 -0
- data/lib/pricing/analyzer.rb +80 -0
- data/lib/pricing/cli.rb +37 -0
- data/lib/pricing/commit.rb +35 -0
- data/lib/pricing/configuration.rb +63 -0
- data/lib/pricing/formatter.rb +33 -0
- data/lib/pricing/helpers.rb +18 -0
- data/lib/pricing/reserve.rb +50 -0
- data/lib/pricing/scraper.rb +38 -0
- data/lib/pricing/symbolize_keys.rb +13 -0
- data/lib/pricing/version.rb +7 -0
- data/lib/tasks/update.rake +16 -0
- data/spec/fixtures/commit.yml +19 -0
- data/spec/fixtures/pricing.yml +7 -0
- data/spec/pricing/analyze_spec.rb +82 -0
- data/spec/pricing/commit_spec.rb +22 -0
- data/spec/pricing/configuration_spec.rb +29 -0
- data/spec/pricing/formatter_spec.rb +35 -0
- data/spec/pricing/reserve_spec.rb +33 -0
- data/spec/pricing/scraper_spec.rb +20 -0
- data/spec/spec_helper.rb +32 -0
- metadata +173 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 211775aa77ec92d64c37f94a021f29e4010bc097
|
4
|
+
data.tar.gz: db713b309bed4b997a60f84ed7c606c093766987
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b98915180ba7cf6e4d3af28555b44ef100c0443c850b3558bd409ed00ae1f585665a64604bf28c508d5dc4266f223d6f21616582754985e740bac6becaa0a674
|
7
|
+
data.tar.gz: 3b42c5e32de624e38bc046cc9c924cb64dd8204cff0b6390f30dd45cd5228804b4527b88a75a407173e45dda62159dc283fadf538ca391e464e1a5f98db195fb
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Konstantin Gredeskoul
|
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,71 @@
|
|
1
|
+
[](http://travis-ci.org/kigster/joyent-cloud-pricing)
|
2
|
+
[](https://codeclimate.com/github/kigster/joyent-cloud-pricing)
|
3
|
+
|
4
|
+
# Joyent Cloud Pricing
|
5
|
+
|
6
|
+
This gem encapsulates several tools around understanding [Joyent](http://joyent.com) pricing model based on a combination of
|
7
|
+
on-demand, as well as commit pricing. It works together with [knife-joyent](https://github.com/joyent/knife-joyent)
|
8
|
+
Chef plugin to show a detailed list of servers with pricing included.
|
9
|
+
|
10
|
+
## Introduction
|
11
|
+
|
12
|
+
Joyent *flavor* is a particular set of RAM, disk and CPU characteristics given to a virtual machine (zone).
|
13
|
+
|
14
|
+
Joyent is unique in that it's [SmartOS](http://smartos.org/) operating system allows dynamic resizing of it's zones without reboot.
|
15
|
+
This means that Joyent customers are much more likely going to be resizing on the fly their zones, so
|
16
|
+
it is common to start with one set of flavors, and end up with a completely different set down the road.
|
17
|
+
|
18
|
+
### Pricing API
|
19
|
+
|
20
|
+
Unfortunately Joyent currently does not provide API for getting prices of their packages (aka "flavors").
|
21
|
+
It is available on the website, but not anywhere else (yet).
|
22
|
+
|
23
|
+
### Commit Discounts
|
24
|
+
|
25
|
+
To make things even more complex, Joyent offers commit discounts to companies that are willing to prepay and
|
26
|
+
commit to hardware for one or three years. Such discounts are done case by case basis, so
|
27
|
+
you would need to contact your Joyent account representative to get the details. These commits are
|
28
|
+
fixed by flavor, so you would be committing to, say, 10 x ```g3-standard-64-smartos``` flavors for a year.
|
29
|
+
|
30
|
+
### The Problem
|
31
|
+
|
32
|
+
If you read the above, you understand that tracking your Joyent monthly pricing ends up being pretty
|
33
|
+
complicated process. This library was written to make this easier, and to allow users of Joyent Cloud
|
34
|
+
quickly check what their monthly pricing should be based on the current footprint, current on-demand
|
35
|
+
pricing, and optionally their pre-pay discounts.
|
36
|
+
|
37
|
+
## Installation
|
38
|
+
|
39
|
+
Add this line to your application's Gemfile:
|
40
|
+
|
41
|
+
gem 'joyent-cloud-pricing'
|
42
|
+
|
43
|
+
And then execute:
|
44
|
+
|
45
|
+
$ bundle
|
46
|
+
|
47
|
+
Or install it yourself as:
|
48
|
+
|
49
|
+
$ gem install joyent-cloud-pricing
|
50
|
+
|
51
|
+
## Usage
|
52
|
+
|
53
|
+
Most recent pricing structure is stored in the YAML file under ```config/joyent_pricing.yml```.
|
54
|
+
|
55
|
+
To update this file, run provided rake task:
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
rake joyent:pricing:update
|
59
|
+
```
|
60
|
+
|
61
|
+
## Contributing
|
62
|
+
|
63
|
+
1. Fork it
|
64
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
65
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
66
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
67
|
+
5. Create new Pull Request
|
68
|
+
|
69
|
+
## Author
|
70
|
+
|
71
|
+
Konstantin Gredeskoul, [@kig on twitter](http://twitter.com/kig), [@kigster on github](http://github.com/kigster)
|
data/Rakefile
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
---
|
2
|
+
:date: '2014-03-27 01:09:02 -0700'
|
3
|
+
:pricing:
|
4
|
+
:g3-standard-0.625-smartos: 0.02
|
5
|
+
:g3-standard-1.75-smartos: 0.056
|
6
|
+
:g3-standard-3.75-smartos: 0.12
|
7
|
+
:g3-standard-7.5-smartos: 0.24
|
8
|
+
:g3-standard-15-smartos: 0.48
|
9
|
+
:g3-standard-30-smartos: 0.96
|
10
|
+
:g3-standard-48-smartos: 1.536
|
11
|
+
:g3-standard-64-smartos: 2.048
|
12
|
+
:g3-standard-80-smartos: 2.56
|
13
|
+
:g3-standard-96-smartos: 3.072
|
14
|
+
:g3-standard-128-smartos-cc: 4.096
|
15
|
+
:g3-highmemory-17.125-smartos: 0.409
|
16
|
+
:g3-highmemory-34.25-smartos: 0.817
|
17
|
+
:g3-highmemory-68.375-smartos: 1.63
|
18
|
+
:g3-highmemory-144-smartos: 3.433
|
19
|
+
:g3-highmemory-256-smartos-cc: 6.102
|
20
|
+
:g3-highcpu-1.75-smartos: 0.127
|
21
|
+
:g3-highcpu-7-smartos: 0.508
|
22
|
+
:g3-highcpu-16-smartos: 1.16
|
23
|
+
:g3-highcpu-24-smartos: 1.739
|
24
|
+
:g3-highcpu-32-smartos-cc: 2.319
|
25
|
+
:g3-highio-15-smartos: 0.76
|
26
|
+
:g3-highio-30-smartos: 1.52
|
27
|
+
:g3-highio-60.5-smartos: 3.067
|
28
|
+
:g3-highio-128-smartos: 6.488
|
29
|
+
:g3-highio-256-smartos-cc: 12.976
|
30
|
+
:g3-highstorage-32-smartos: 0.863
|
31
|
+
:g3-highstorage-64-smartos: 1.726
|
32
|
+
:g3-highstorage-128-smartos-cc: 3.451
|
33
|
+
:g3-standard-0.625-kvm: 0.02
|
34
|
+
:g3-standard-1.75-kvm: 0.056
|
35
|
+
:g3-standard-3.75-kvm: 0.12
|
36
|
+
:g3-standard-7.5-kvm: 0.24
|
37
|
+
:g3-standard-15-kvm: 0.48
|
38
|
+
:g3-standard-30-kvm: 0.96
|
39
|
+
:g3-highmemory-17.125-kvm: 0.409
|
40
|
+
:g3-highmemory-34.25-kvm: 0.817
|
41
|
+
:g3-highmemory-68.375-kvm: 1.63
|
42
|
+
:g3-highcpu-1.75-kvm: 0.127
|
43
|
+
:g3-highcpu-7-kvm: 0.508
|
44
|
+
:g3-highio-15-kvm: 0.76
|
45
|
+
:g3-highio-30-kvm: 1.52
|
46
|
+
:g3-highio-60.5-kvm: 3.067
|
47
|
+
:g3-highstorage-32-kvm: 0.863
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'pricing/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "joyent-cloud-pricing"
|
8
|
+
spec.version = Joyent::Cloud::Pricing::VERSION
|
9
|
+
spec.authors = ["Konstantin Gredeskoul"]
|
10
|
+
spec.email = ["kigster@gmail.com"]
|
11
|
+
spec.summary = %q{Tools for calculating monthly and yearly price of infrastructure hosted on Joyent Public Cloud.}
|
12
|
+
spec.description = %q{Various set of tools and helpers to calculate infrastructure footprint and cost on Joyent Cloud. Supports commit discounts.}
|
13
|
+
spec.homepage = "https://github.com/kigster/joyent-cloud-pricing"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_dependency "nokogiri"
|
23
|
+
spec.add_dependency 'mixlib-cli'
|
24
|
+
|
25
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
26
|
+
spec.add_development_dependency "rake"
|
27
|
+
|
28
|
+
spec.add_development_dependency "rspec"
|
29
|
+
spec.add_development_dependency "rspec-mocks"
|
30
|
+
end
|
data/lib/pricing.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require "pricing/version"
|
2
|
+
require "pricing/symbolize_keys"
|
3
|
+
|
4
|
+
module Joyent
|
5
|
+
module Cloud
|
6
|
+
module Pricing
|
7
|
+
|
8
|
+
PRICING_FILENAME = File.expand_path('../../config/joyent_pricing.yml', __FILE__)
|
9
|
+
COMMIT_FILENAME = 'config/commit_pricing.yml'
|
10
|
+
JOYENT_URL = 'http://www.joyent.com/products/compute-service/pricing'
|
11
|
+
HOURS_PER_MONTH = 720
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
require "pricing/helpers"
|
18
|
+
require "pricing/configuration"
|
19
|
+
require "pricing/scraper"
|
20
|
+
require "pricing/formatter"
|
21
|
+
require "pricing/commit"
|
22
|
+
require "pricing/analyzer"
|
23
|
+
|
24
|
+
require "pricing/cli"
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require_relative 'commit'
|
2
|
+
|
3
|
+
module Joyent::Cloud::Pricing
|
4
|
+
class Analyzer
|
5
|
+
|
6
|
+
attr_accessor :commit, :zone_list
|
7
|
+
|
8
|
+
def initialize(commit, flavors = [])
|
9
|
+
@commit = commit
|
10
|
+
@zone_list = count_dupes(flavors).symbolize_keys
|
11
|
+
end
|
12
|
+
|
13
|
+
# Zones that are not on commit
|
14
|
+
def excess_zone_list
|
15
|
+
h = {}
|
16
|
+
zone_list.each_pair { |flavor, count| diff = count - quantity_for(flavor); h[flavor] = diff if diff > 0 }
|
17
|
+
h
|
18
|
+
end
|
19
|
+
|
20
|
+
# Zones that are committed, but do not exist
|
21
|
+
def over_provisioned_zone_list
|
22
|
+
h = {}
|
23
|
+
zone_list.each_pair { |flavor, count| diff = count - quantity_for(flavor); h[flavor] = -diff if diff < 0 }
|
24
|
+
h
|
25
|
+
end
|
26
|
+
|
27
|
+
# Non-discounted full price
|
28
|
+
def monthly_full_price
|
29
|
+
monthly_full_price_for(zone_list)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Excess zones cost this much
|
33
|
+
def excess_monthly_price
|
34
|
+
monthly_full_price_for(excess_zone_list)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Monthly for all of the commits
|
38
|
+
def commit_monthly_price
|
39
|
+
commit.monthly_price
|
40
|
+
end
|
41
|
+
|
42
|
+
# Commits + excess non reserved zones
|
43
|
+
def total_monthly_price
|
44
|
+
excess_monthly_price + commit_monthly_price
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def monthly_full_price_for zones
|
51
|
+
total_price_for zones do |flavor|
|
52
|
+
pricing.monthly(flavor)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def total_price_for zones, &block
|
57
|
+
zones.keys.inject(0) do |sum, flavor|
|
58
|
+
sum += zones[flavor] * yield(flavor); sum
|
59
|
+
end.round(2)
|
60
|
+
end
|
61
|
+
|
62
|
+
def quantity_for flavor
|
63
|
+
r = commit.reserve_for(flavor)
|
64
|
+
r ? r.quantity : 0
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
def count_dupes(ary)
|
69
|
+
h = Hash.new(0)
|
70
|
+
ary.each { |v| h.store(v, h[v]+1) }
|
71
|
+
h.symbolize_keys
|
72
|
+
end
|
73
|
+
|
74
|
+
def pricing
|
75
|
+
Joyent::Cloud::Pricing::Configuration.instance
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
data/lib/pricing/cli.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'mixlib/cli'
|
2
|
+
|
3
|
+
module Joyent
|
4
|
+
module Cloud
|
5
|
+
module Pricing
|
6
|
+
class CLI
|
7
|
+
include Mixlib::CLI
|
8
|
+
|
9
|
+
banner 'Usage: joyent-price-helper [--commit <path-to-commit.yml] '
|
10
|
+
|
11
|
+
option :config_file,
|
12
|
+
short: '-c COMMIT_FILE',
|
13
|
+
long: '--commit COMMIT_FILE',
|
14
|
+
description: 'Path to the config file for commit pricing (YML), default is "config/commit_pricing.yml"',
|
15
|
+
required: false
|
16
|
+
|
17
|
+
option :help,
|
18
|
+
short: '-h',
|
19
|
+
long: '--help',
|
20
|
+
description: 'Show this message',
|
21
|
+
on: :tail,
|
22
|
+
boolean: true,
|
23
|
+
show_options: true,
|
24
|
+
exit: 0
|
25
|
+
|
26
|
+
attr_reader :args
|
27
|
+
|
28
|
+
def run argv = ARGV
|
29
|
+
parse_options(argv)
|
30
|
+
raise "Not implemented just yet"
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require_relative 'reserve'
|
3
|
+
|
4
|
+
module Joyent::Cloud::Pricing
|
5
|
+
class Commit
|
6
|
+
class << self
|
7
|
+
def from_yaml(filename = COMMIT_FILENAME)
|
8
|
+
new(YAML.load(File.read(filename))['reserved'])
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# map of image names to prices
|
13
|
+
attr_accessor :reserves
|
14
|
+
|
15
|
+
def initialize(hash = {})
|
16
|
+
@config = hash.symbolize_keys
|
17
|
+
self.reserves = {}
|
18
|
+
@config.each_pair do |flavor, config|
|
19
|
+
self.reserves[flavor] = Reserve.new(flavor, config)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def reserve_for flavor
|
24
|
+
reserves[flavor.to_sym]
|
25
|
+
end
|
26
|
+
|
27
|
+
def monthly_price
|
28
|
+
reserves.values.inject(0) do |sum, reserve|
|
29
|
+
sum += reserve.monthly * reserve.quantity; sum
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require_relative 'helpers'
|
3
|
+
|
4
|
+
module Joyent::Cloud::Pricing
|
5
|
+
class Configuration
|
6
|
+
@@lock = Mutex.new # just in case
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def instance(reload = false)
|
10
|
+
@last_instance = from_yaml if reload || @last_instance.nil?
|
11
|
+
@last_instance
|
12
|
+
end
|
13
|
+
|
14
|
+
def from_yaml(filename = PRICING_FILENAME)
|
15
|
+
set_instance(new(YAML.load(File.read(filename))[:pricing]))
|
16
|
+
end
|
17
|
+
|
18
|
+
def from_url(url = JOYENT_URL)
|
19
|
+
set_instance(new(Joyent::Cloud::Pricing::Scraper.new.scrape(url)))
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
def set_instance(config)
|
24
|
+
@@lock.synchronize do
|
25
|
+
@last_instance = config
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
include Helpers
|
31
|
+
|
32
|
+
# map of image names to prices
|
33
|
+
attr_accessor :config
|
34
|
+
|
35
|
+
def initialize(hash = {})
|
36
|
+
@config = hash.symbolize_keys
|
37
|
+
end
|
38
|
+
|
39
|
+
def [] flavor
|
40
|
+
self.config[flavor.to_sym]
|
41
|
+
end
|
42
|
+
|
43
|
+
def monthly flavor
|
44
|
+
f = self[flavor]
|
45
|
+
if f.nil?
|
46
|
+
STDERR.puts "WARNING: can't find flavor #{flavor}, assuming 0"
|
47
|
+
0
|
48
|
+
else
|
49
|
+
monthly_from_hourly f
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
def save_yaml(filename = PRICING_FILENAME)
|
55
|
+
File.open(filename, 'w') do |f|
|
56
|
+
YAML.dump({:date => Time.now.to_s,
|
57
|
+
:pricing => config, }, f)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
require 'nokogiri'
|
3
|
+
require_relative 'helpers'
|
4
|
+
|
5
|
+
module Joyent
|
6
|
+
module Cloud
|
7
|
+
module Pricing
|
8
|
+
class Formatter
|
9
|
+
|
10
|
+
include Helpers
|
11
|
+
|
12
|
+
attr_reader :config
|
13
|
+
|
14
|
+
def initialize(config)
|
15
|
+
@config = config
|
16
|
+
end
|
17
|
+
|
18
|
+
def monthly_price(flavor)
|
19
|
+
monthly_from_hourly(config[flavor] || 0)
|
20
|
+
end
|
21
|
+
|
22
|
+
def format_price(value, width = 0)
|
23
|
+
value = 0 if value.nil?
|
24
|
+
value > 0 ? sprintf("%#{width}s", currency_format(sprintf("$%.2f", value))) : " " * width
|
25
|
+
end
|
26
|
+
|
27
|
+
def format_monthly_price(flavor, width = 0)
|
28
|
+
format_price(monthly_price(flavor), width)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Joyent
|
2
|
+
module Cloud
|
3
|
+
module Pricing
|
4
|
+
module Helpers
|
5
|
+
|
6
|
+
def monthly_from_hourly price
|
7
|
+
price * HOURS_PER_MONTH
|
8
|
+
end
|
9
|
+
|
10
|
+
# Returns string formatted with commas in the middle, such as "9,999,999"
|
11
|
+
def currency_format string
|
12
|
+
while string.sub!(/(\d+)(\d\d\d)/, '\1,\2'); end
|
13
|
+
string
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Joyent::Cloud::Pricing
|
2
|
+
class Reserve
|
3
|
+
include Helpers
|
4
|
+
|
5
|
+
attr_accessor :flavor, :monthly, :prepay, :years, :quantity
|
6
|
+
|
7
|
+
def initialize(flavor, config)
|
8
|
+
@flavor = flavor.to_sym
|
9
|
+
@prepay = config[:prepay].to_f
|
10
|
+
@monthly = config[:monthly].to_f
|
11
|
+
@quantity = config[:quantity].to_i
|
12
|
+
@years = config[:years]
|
13
|
+
end
|
14
|
+
|
15
|
+
def monthly_averaged
|
16
|
+
(total_payout / (@years * 12)).round(2)
|
17
|
+
end
|
18
|
+
|
19
|
+
def total_payout
|
20
|
+
(@prepay + @years * 12 * @monthly).round(2)
|
21
|
+
end
|
22
|
+
|
23
|
+
def total_discount
|
24
|
+
(monthly_full_price * 12 * years).round(2) - total_payout
|
25
|
+
end
|
26
|
+
|
27
|
+
def monthly_discount
|
28
|
+
(total_discount / 12 / years).round(2)
|
29
|
+
end
|
30
|
+
|
31
|
+
def monthly_discount_percent
|
32
|
+
(100 * monthly_discount / monthly_full_price).round(2)
|
33
|
+
end
|
34
|
+
|
35
|
+
def monthly_full_price
|
36
|
+
pricing.monthly flavor
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_hash
|
40
|
+
{prepay: prepay, monthly: monthly, years: years, quantity: quantity}
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def pricing
|
46
|
+
Joyent::Cloud::Pricing::Configuration.instance
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
require 'nokogiri'
|
3
|
+
|
4
|
+
module Joyent::Cloud::Pricing
|
5
|
+
class Scraper
|
6
|
+
def scrape(url = JOYENT_URL)
|
7
|
+
Parser.new(Nokogiri::HTML(open(url))).result
|
8
|
+
end
|
9
|
+
|
10
|
+
def load_from_file(file)
|
11
|
+
Parser.new(Nokogiri::HTML(File.read(file))).result
|
12
|
+
end
|
13
|
+
|
14
|
+
class Parser < Struct.new(:doc)
|
15
|
+
class PriceTuple < Struct.new(:os, :cost, :flavor); end
|
16
|
+
|
17
|
+
def result
|
18
|
+
config = Hash.new
|
19
|
+
self.doc.css("ul.full-specs").each do |ul|
|
20
|
+
tuple = extract_price(ul)
|
21
|
+
next if tuple.cost == "N/A"
|
22
|
+
next if tuple.flavor =~ /kvm/ && tuple.os !~ /linux/i
|
23
|
+
config[tuple.flavor]= tuple.cost.to_f
|
24
|
+
end
|
25
|
+
config
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def extract_price(ul)
|
31
|
+
lis = ul.css("span").map(&:content)
|
32
|
+
# grab last two <li> elements in each <ul class="full-spec"> block
|
33
|
+
#PriceTuple.new(lis[-3], lis[-2].gsub(/^\$/, ''), lis[-1])
|
34
|
+
PriceTuple.new(lis[-3], lis[-2].gsub(/^\$/, ''), lis[-1])
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'pricing'
|
2
|
+
require 'yaml'
|
3
|
+
namespace :joyent do
|
4
|
+
namespace :pricing do
|
5
|
+
desc "Refresh pricing configuration from the Joyent website"
|
6
|
+
task :update do
|
7
|
+
load_from_url = Joyent::Cloud::Pricing::JOYENT_URL
|
8
|
+
save_to_file = Joyent::Cloud::Pricing::PRICING_FILENAME
|
9
|
+
|
10
|
+
STDOUT.puts "downloading latest prices from #{load_from_url}"
|
11
|
+
config = Joyent::Cloud::Pricing::Configuration.from_url(load_from_url)
|
12
|
+
config.save_yaml(save_to_file)
|
13
|
+
STDOUT.puts "saved #{config.config.keys.size} image prices to #{save_to_file}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
defaults: &defaults
|
2
|
+
years: 1
|
3
|
+
|
4
|
+
reserved:
|
5
|
+
"g3-highcpu-32-smartos-cc":
|
6
|
+
<<: *defaults
|
7
|
+
prepay: 8000.00
|
8
|
+
monthly: 500
|
9
|
+
quantity: 10
|
10
|
+
"g3-highmemory-17.125-smartos":
|
11
|
+
<<: *defaults
|
12
|
+
prepay: 800.00
|
13
|
+
monthly: 60
|
14
|
+
quantity: 12
|
15
|
+
"g3-highio-60.5-smartos":
|
16
|
+
<<: *defaults
|
17
|
+
prepay: 1800.00
|
18
|
+
monthly: 600
|
19
|
+
quantity: 5
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Joyent::Cloud::Pricing::Analyze' do
|
4
|
+
|
5
|
+
let(:flavors) { %w(
|
6
|
+
g3-highcpu-16-smartos
|
7
|
+
g3-highcpu-16-smartos
|
8
|
+
g3-standard-30-smartos
|
9
|
+
g3-highcpu-32-smartos-cc
|
10
|
+
g3-highcpu-32-smartos-cc
|
11
|
+
g3-highcpu-32-smartos-cc
|
12
|
+
g3-highcpu-32-smartos-cc
|
13
|
+
g3-highcpu-32-smartos-cc
|
14
|
+
g3-highcpu-32-smartos-cc
|
15
|
+
g3-highcpu-32-smartos-cc
|
16
|
+
g3-highcpu-32-smartos-cc
|
17
|
+
g3-highcpu-32-smartos-cc
|
18
|
+
g3-highcpu-32-smartos-cc
|
19
|
+
g3-highcpu-32-smartos-cc
|
20
|
+
g3-highcpu-32-smartos-cc
|
21
|
+
g3-highcpu-7-smartos
|
22
|
+
g3-highcpu-7-smartos
|
23
|
+
g3-highio-60.5-smartos
|
24
|
+
g3-highio-60.5-smartos
|
25
|
+
g3-highio-60.5-smartos
|
26
|
+
g3-highio-60.5-smartos
|
27
|
+
g3-highmemory-17.125-smartos
|
28
|
+
g3-highmemory-17.125-smartos
|
29
|
+
g3-highmemory-17.125-smartos
|
30
|
+
g3-highmemory-17.125-smartos
|
31
|
+
g3-highmemory-17.125-smartos
|
32
|
+
g3-highmemory-17.125-smartos
|
33
|
+
g3-highmemory-17.125-smartos
|
34
|
+
g3-highmemory-17.125-smartos
|
35
|
+
g3-highmemory-17.125-smartos
|
36
|
+
g3-highmemory-17.125-smartos
|
37
|
+
g3-highmemory-17.125-smartos
|
38
|
+
g3-highmemory-17.125-smartos
|
39
|
+
) }
|
40
|
+
let(:commit) { Joyent::Cloud::Pricing::Commit.from_yaml 'spec/fixtures/commit.yml' }
|
41
|
+
let(:analyzer) { Joyent::Cloud::Pricing::Analyzer.new(commit, flavors) }
|
42
|
+
|
43
|
+
# need to have pricing so that it reloads from real price TODO: fix this
|
44
|
+
before do
|
45
|
+
Joyent::Cloud::Pricing::Configuration.from_yaml
|
46
|
+
end
|
47
|
+
|
48
|
+
it '#initialize' do
|
49
|
+
expect(analyzer.zone_list).to_not be_empty
|
50
|
+
expect(analyzer.zone_list).to eql (
|
51
|
+
{:"g3-highcpu-16-smartos" => 2,
|
52
|
+
:"g3-highcpu-32-smartos-cc" => 12,
|
53
|
+
:"g3-highcpu-7-smartos" => 2,
|
54
|
+
:"g3-highio-60.5-smartos" => 4,
|
55
|
+
:"g3-highmemory-17.125-smartos" => 12,
|
56
|
+
:"g3-standard-30-smartos" => 1
|
57
|
+
})
|
58
|
+
end
|
59
|
+
|
60
|
+
it '#monthly_full_price' do
|
61
|
+
expect(analyzer.monthly_full_price).to eql(35496.0)
|
62
|
+
end
|
63
|
+
|
64
|
+
it '#excess_zone_list' do
|
65
|
+
expect(analyzer.excess_zone_list).to eql(
|
66
|
+
{:"g3-highcpu-16-smartos" => 2,
|
67
|
+
:"g3-highcpu-32-smartos-cc" => 2,
|
68
|
+
:"g3-highcpu-7-smartos" => 2,
|
69
|
+
:"g3-standard-30-smartos" => 1
|
70
|
+
})
|
71
|
+
end
|
72
|
+
|
73
|
+
it '#excess_monthly_price' do
|
74
|
+
expect(analyzer.excess_monthly_price).to eql( 6432.48 )
|
75
|
+
end
|
76
|
+
|
77
|
+
it '#over_provisioned_zone_list' do
|
78
|
+
expect(analyzer.over_provisioned_zone_list).to eql( {:"g3-highio-60.5-smartos" => 1 })
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Joyent::Cloud::Pricing::Commit' do
|
4
|
+
let(:commit) { Joyent::Cloud::Pricing::Commit.from_yaml 'spec/fixtures/commit.yml' }
|
5
|
+
|
6
|
+
let(:expected_commit) { {
|
7
|
+
'g3-highcpu-32-smartos-cc' => {prepay: 8000.0, monthly: 500.0, years: 1, quantity: 10},
|
8
|
+
'g3-highmemory-17.125-smartos' => {prepay: 800.0, monthly: 60.0, years: 1, quantity: 12},
|
9
|
+
'g3-highio-60.5-smartos' => {prepay: 1800.0, monthly: 600.0, years: 1, quantity: 5}
|
10
|
+
} }
|
11
|
+
|
12
|
+
it 'should correctly load commit from the file' do
|
13
|
+
expected_commit.keys.each do |flavor|
|
14
|
+
expect(expected_commit[flavor]).to eql(commit.reserve_for(flavor).to_hash)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
it '#monthly_price' do
|
19
|
+
expect(commit.monthly_price).to eql(8720.0)
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ' Joyent::Cloud::Pricing::Configuration' do
|
4
|
+
let(:expected_prices) { {
|
5
|
+
"g3-standard-48-smartos" => 1.536,
|
6
|
+
"g3-standard-0.625-smartos" => 0.02,
|
7
|
+
"g3-standard-30-kvm" => 0.960} }
|
8
|
+
|
9
|
+
it "should load pricing configuration hash from YAML" do
|
10
|
+
config = Joyent::Cloud::Pricing::Configuration.from_yaml 'spec/fixtures/pricing.yml'
|
11
|
+
expected_prices.keys.each do |flavor|
|
12
|
+
expect(config[flavor]).to eql(expected_prices[flavor])
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
context "#instance" do
|
17
|
+
it "should be able to create new instance, but remember the last once" do
|
18
|
+
c1 = Joyent::Cloud::Pricing::Configuration.from_yaml 'spec/fixtures/pricing.yml'
|
19
|
+
c2 = Joyent::Cloud::Pricing::Configuration.from_yaml 'spec/fixtures/pricing.yml'
|
20
|
+
|
21
|
+
expect(Joyent::Cloud::Pricing::Configuration.instance).to eql(c2)
|
22
|
+
expect(Joyent::Cloud::Pricing::Configuration.instance).not_to eql(c1)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should have instance set" do
|
26
|
+
expect(Joyent::Cloud::Pricing::Configuration.instance).not_to be_nil
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe' Joyent::Cloud::Pricing::Formatter' do
|
4
|
+
|
5
|
+
let(:config) { {"g3-standard-48-smartos" => 1.536,
|
6
|
+
"g3-standard-0.625-smartos" => 0.02,
|
7
|
+
"g3-standard-30-kvm" => 0.960} }
|
8
|
+
|
9
|
+
let(:formatter) { Joyent::Cloud::Pricing::Formatter.new(config) }
|
10
|
+
|
11
|
+
context "#format_monthly_price" do
|
12
|
+
it "should return properly formatted monthly price" do
|
13
|
+
expect(formatter.format_monthly_price "g3-standard-0.625-smartos").to eql("$14.40")
|
14
|
+
expect(formatter.format_monthly_price "g3-standard-30-kvm").to eql("$691.20")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
context "#monthly_formatted_price_for_flavor" do
|
18
|
+
it "should return properly formatted monthly price" do
|
19
|
+
expect(formatter.format_monthly_price "g3-standard-48-smartos", 10).to eql(" $1,105.92")
|
20
|
+
end
|
21
|
+
it "should return blank when no match was found" do
|
22
|
+
expect(formatter.format_monthly_price "asdfkasdfasdlfkjasl;dkjf").to eql("")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
context "#format_price" do
|
26
|
+
it "should return properly formatted price" do
|
27
|
+
expect(formatter.format_price 24566.34, 10).to eql("$24,566.34")
|
28
|
+
expect(formatter.format_price 4566.34, 10).to eql(" $4,566.34")
|
29
|
+
end
|
30
|
+
it "should return blank string of given width for 0 or nil" do
|
31
|
+
expect(formatter.format_price 0, 10).to eql(" ")
|
32
|
+
expect(formatter.format_price nil, 10).to eql(" ")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Joyent::Cloud::Pricing::Reserve' do
|
4
|
+
let(:pricing) { Joyent::Cloud::Pricing::Configuration.from_yaml }
|
5
|
+
let(:flavor) { "g3-highcpu-32-smartos-cc" }
|
6
|
+
let(:reserve) {
|
7
|
+
Joyent::Cloud::Pricing::Reserve.new(
|
8
|
+
flavor, prepay: 8000.0, monthly: 500, quantity: 10, years: 1)
|
9
|
+
}
|
10
|
+
|
11
|
+
it "should set members correctly" do
|
12
|
+
expect(reserve.prepay).to eql(8000.0)
|
13
|
+
expect(reserve.monthly).to eql(500.0)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should calculate averaged monthly price" do
|
17
|
+
expect(reserve.monthly_averaged).to eql(1166.67)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should calculate total payout" do
|
21
|
+
expect(reserve.total_payout).to eql((8000 + 12*500).round(2))
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should calculate monthly discount" do
|
25
|
+
# important to call pricing to load it from real YAML,
|
26
|
+
# not the fixtures
|
27
|
+
expect(pricing.monthly flavor).to eq(1669.68)
|
28
|
+
|
29
|
+
expect(reserve.monthly_discount).to eql(503.01)
|
30
|
+
expect(reserve.monthly_discount_percent).to eql((100 * 503.01 / 1669.68).round(2))
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Joyent::Cloud::Pricing::Scraper' do
|
4
|
+
context "scraping from URL"
|
5
|
+
let(:scraper) { Joyent::Cloud::Pricing::Scraper.new }
|
6
|
+
|
7
|
+
let(:prices) { {"g3-standard-48-smartos" => 1.536,
|
8
|
+
"g3-standard-0.625-smartos" => 0.02,
|
9
|
+
"g3-standard-30-kvm" => 0.960} }
|
10
|
+
|
11
|
+
before do
|
12
|
+
@config = scraper.scrape
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should load pricing configuration hash from Joyent Website" do
|
16
|
+
prices.keys.each do |flavor|
|
17
|
+
expect(@config[flavor]).to eql(prices[flavor])
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
|
5
|
+
require 'pricing'
|
6
|
+
|
7
|
+
# Dir['spec/support/**/*.rb'].each { |filename| require_relative "../#{filename}" }
|
8
|
+
|
9
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
10
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
11
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
12
|
+
# loaded once.
|
13
|
+
#
|
14
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
15
|
+
RSpec.configure do |config|
|
16
|
+
# Limit the spec run to only specs with the focus metadata. If no specs have
|
17
|
+
# the filtering metadata and `run_all_when_everything_filtered = true` then
|
18
|
+
# all specs will run.
|
19
|
+
#config.filter_run :focus
|
20
|
+
|
21
|
+
# Run all specs when none match the provided filter. This works well in
|
22
|
+
# conjunction with `config.filter_run :focus`, as it will run the entire
|
23
|
+
# suite when no specs have `:filter` metadata.
|
24
|
+
config.run_all_when_everything_filtered = true
|
25
|
+
|
26
|
+
# Run specs in random order to surface order dependencies. If you find an
|
27
|
+
# order dependency and want to debug it, you can fix the order by providing
|
28
|
+
# the seed, which is printed after each run.
|
29
|
+
# --seed 1234
|
30
|
+
config.order = 'random'
|
31
|
+
end
|
32
|
+
|
metadata
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: joyent-cloud-pricing
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Konstantin Gredeskoul
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-03-27 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: nokogiri
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: mixlib-cli
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.5'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.5'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec-mocks
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description: Various set of tools and helpers to calculate infrastructure footprint
|
98
|
+
and cost on Joyent Cloud. Supports commit discounts.
|
99
|
+
email:
|
100
|
+
- kigster@gmail.com
|
101
|
+
executables:
|
102
|
+
- joyent-price-helper
|
103
|
+
extensions: []
|
104
|
+
extra_rdoc_files: []
|
105
|
+
files:
|
106
|
+
- ".gitignore"
|
107
|
+
- ".rspec"
|
108
|
+
- ".travis.yml"
|
109
|
+
- Gemfile
|
110
|
+
- LICENSE.txt
|
111
|
+
- README.md
|
112
|
+
- Rakefile
|
113
|
+
- bin/joyent-price-helper
|
114
|
+
- config/commit_pricing.yml.example
|
115
|
+
- config/joyent_pricing.yml
|
116
|
+
- joyent-cloud-pricing.gemspec
|
117
|
+
- lib/pricing.rb
|
118
|
+
- lib/pricing/analyzer.rb
|
119
|
+
- lib/pricing/cli.rb
|
120
|
+
- lib/pricing/commit.rb
|
121
|
+
- lib/pricing/configuration.rb
|
122
|
+
- lib/pricing/formatter.rb
|
123
|
+
- lib/pricing/helpers.rb
|
124
|
+
- lib/pricing/reserve.rb
|
125
|
+
- lib/pricing/scraper.rb
|
126
|
+
- lib/pricing/symbolize_keys.rb
|
127
|
+
- lib/pricing/version.rb
|
128
|
+
- lib/tasks/update.rake
|
129
|
+
- spec/fixtures/commit.yml
|
130
|
+
- spec/fixtures/pricing.yml
|
131
|
+
- spec/pricing/analyze_spec.rb
|
132
|
+
- spec/pricing/commit_spec.rb
|
133
|
+
- spec/pricing/configuration_spec.rb
|
134
|
+
- spec/pricing/formatter_spec.rb
|
135
|
+
- spec/pricing/reserve_spec.rb
|
136
|
+
- spec/pricing/scraper_spec.rb
|
137
|
+
- spec/spec_helper.rb
|
138
|
+
homepage: https://github.com/kigster/joyent-cloud-pricing
|
139
|
+
licenses:
|
140
|
+
- MIT
|
141
|
+
metadata: {}
|
142
|
+
post_install_message:
|
143
|
+
rdoc_options: []
|
144
|
+
require_paths:
|
145
|
+
- lib
|
146
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
147
|
+
requirements:
|
148
|
+
- - ">="
|
149
|
+
- !ruby/object:Gem::Version
|
150
|
+
version: '0'
|
151
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
152
|
+
requirements:
|
153
|
+
- - ">="
|
154
|
+
- !ruby/object:Gem::Version
|
155
|
+
version: '0'
|
156
|
+
requirements: []
|
157
|
+
rubyforge_project:
|
158
|
+
rubygems_version: 2.2.0
|
159
|
+
signing_key:
|
160
|
+
specification_version: 4
|
161
|
+
summary: Tools for calculating monthly and yearly price of infrastructure hosted on
|
162
|
+
Joyent Public Cloud.
|
163
|
+
test_files:
|
164
|
+
- spec/fixtures/commit.yml
|
165
|
+
- spec/fixtures/pricing.yml
|
166
|
+
- spec/pricing/analyze_spec.rb
|
167
|
+
- spec/pricing/commit_spec.rb
|
168
|
+
- spec/pricing/configuration_spec.rb
|
169
|
+
- spec/pricing/formatter_spec.rb
|
170
|
+
- spec/pricing/reserve_spec.rb
|
171
|
+
- spec/pricing/scraper_spec.rb
|
172
|
+
- spec/spec_helper.rb
|
173
|
+
has_rdoc:
|