knife-joyent 0.4.5 → 0.4.8

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -33,6 +33,7 @@ Currently available commands:
33
33
  knife joyent server resize <server_id> -f <flavor>
34
34
  knife joyent server start <server_id>
35
35
  knife joyent server stop <server_id>
36
+ knife joyent server pricing
36
37
  knife joyent server metadata update <server_id> -m <json>
37
38
  knife joyent server metadata delete <server_id> <options>
38
39
  knife joyent snapshot create <server_id> <snapshot_name>
data/knife-joyent.gemspec CHANGED
@@ -16,9 +16,10 @@ Gem::Specification.new do |s|
16
16
  s.files = `git ls-files`.split("\n")
17
17
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
18
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
- s.add_dependency "fog", "~> 1.15.0"
19
+ s.add_dependency "fog", "~> 1.21.0"
20
20
  s.add_dependency "multi_json", "~> 1.7"
21
- s.add_dependency "chef", ">= 0.10.10"
21
+ s.add_dependency "chef", "~> 11.6"
22
+ s.add_dependency "joyent-cloud-pricing", ">= 1.0.3"
22
23
 
23
24
  s.add_development_dependency 'rspec'
24
25
  s.add_development_dependency 'guard-rspec'
@@ -1,5 +1,5 @@
1
1
  require 'chef/knife'
2
- require_relative '../../knife-joyent/pricing'
2
+ require 'pricing'
3
3
 
4
4
  class Chef
5
5
  class Knife
@@ -73,10 +73,6 @@ class Chef
73
73
  end
74
74
  end
75
75
 
76
- def pricing
77
- @pricing ||= KnifeJoyent::Pricing::Config.new
78
- end
79
-
80
76
  def locate_config_value(key)
81
77
  key = key.to_sym
82
78
  config[key] || Chef::Config[:knife][key]
@@ -239,24 +239,28 @@ class Chef
239
239
  Chef::Log.debug("Bootstrap distro = #{config[:distro]}")
240
240
  bootstrap.config[:distro] = config[:distro]
241
241
 
242
- Chef::Log.debug("Bootstrap use_sudo = #{config[:use_sudo]}")
243
- bootstrap.config[:use_sudo] = true unless config[:ssh_user] == 'root'
242
+ if config[:ssh_user] == 'root'
243
+ bootstrap.config[:use_sudo] = false
244
+ else
245
+ bootstrap.config[:use_sudo] = true
246
+ end
247
+
248
+ Chef::Log.debug("Bootstrap use_sudo = #{bootstrap.config[:use_sudo]}")
244
249
 
245
- Chef::Log.debug("Bootstrap environment = #{config[:environment]}")
246
250
  bootstrap.config[:environment] = config[:environment]
251
+ Chef::Log.debug("Bootstrap environment = #{bootstrap.config[:environment]}")
247
252
 
248
- Chef::Log.debug("Bootstrap no_host_key_verify = #{config[:no_host_key_verify]}")
249
253
  bootstrap.config[:no_host_key_verify] = config[:no_host_key_verify]
254
+ Chef::Log.debug("Bootstrap no_host_key_verify = #{bootstrap.config[:no_host_key_verify]}")
250
255
 
251
- Chef::Log.debug("Bootstrap identity_file = #{config[:identity_file]}")
252
256
  bootstrap.config[:identity_file] = config[:identity_file]
257
+ Chef::Log.debug("Bootstrap identity_file = #{bootstrap.config[:identity_file]}")
253
258
 
254
- Chef::Log.debug("Bootstrap ssh_gateway= #{config[:ssh_gateway]}")
255
259
  bootstrap.config[:ssh_gateway] = config[:ssh_gateway]
260
+ Chef::Log.debug("Bootstrap ssh_gateway= #{bootstrap.config[:ssh_gateway]}")
256
261
 
257
-
258
- Chef::Log.debug("Bootstrap json_attributes = #{config[:json_attributes]}")
259
262
  bootstrap.config[:first_boot_attributes] = config[:json_attributes]
263
+ Chef::Log.debug("Bootstrap json_attributes = #{bootstrap.config[:json_attributes]}")
260
264
 
261
265
  bootstrap
262
266
  end
@@ -6,11 +6,42 @@ class Chef
6
6
 
7
7
  include Knife::JoyentBase
8
8
 
9
+ option :show,
10
+ :long => '--show field1,field1,.',
11
+ :description => 'Show additional fields. Supported: compute_node, tags',
12
+ :proc => Proc.new {|key| Chef::Config[:knife][:show] = key.to_s.split(/,/).map(&:to_sym)}
13
+
14
+ option :sort,
15
+ :long => '--sort field',
16
+ :description => 'Sort by field, default is name, supported also: compute_node, price',
17
+ :proc => Proc.new {|key| Chef::Config[:knife][:sort] = key.to_sym}
18
+
9
19
  banner "knife joyent server list <options>"
10
20
 
21
+ ADDITIONAL_FIELDS = [:compute_node, :tags]
22
+
23
+ PRICE_COLUMN_WIDTH = 11
24
+
11
25
  def run
12
- columns = 11
13
- price_column_width = 11
26
+
27
+ sort_matrix = {
28
+ :name => lambda do |a, b|
29
+ (a.name || '') <=> (b.name || '')
30
+ end,
31
+ :compute_node => lambda do |a, b|
32
+ if a.attributes && b.attributes
33
+ (a.attributes["compute_node"]) <=> (b.attributes["compute_node"])
34
+ end
35
+ end,
36
+ :price => lambda do |a, b|
37
+ if a.package && b.package
38
+ pricing.monthly_price(a.package) <=> pricing.monthly_price(b.package)
39
+ end
40
+ end
41
+ }
42
+
43
+ columns = 10 + num_of_extra_keys
44
+
14
45
 
15
46
  servers = [
16
47
  ui.color('ID', :bold),
@@ -22,15 +53,22 @@ class Chef
22
53
  ui.color('IPs', :bold),
23
54
  ui.color(' RAM', :bold),
24
55
  ui.color(' Disk', :bold),
25
- ui.color('Price $/Month'),
26
- ui.color('Tags', :bold)
56
+ ui.color('Price $/Month')
27
57
  ]
28
58
 
29
- total_cost = 0
59
+ servers << ui.color('Compute Node', :bold) if show?(:compute_node)
60
+ servers << ui.color('Tags', :bold) if show?(:tags)
61
+
62
+ total_monthly_price = 0
63
+
64
+ prev_compute_node = nil # only needed if sorting by compute_node
65
+
66
+ self.connection.servers.sort(&sort_by(sort_matrix, sort_field)).each do |s|
67
+
68
+ compute_node = s.respond_to?(:attributes) ? s.attributes["compute_node"] : 'unknown'
69
+ columns.times { servers << "" } if sort_field == :compute_node && prev_compute_node && prev_compute_node != compute_node
70
+ prev_compute_node = compute_node
30
71
 
31
- self.connection.servers.sort do |a, b|
32
- (a.name || '') <=> (b.name || '')
33
- end.each do |s|
34
72
  servers << s.id.to_s
35
73
  servers << s.name
36
74
 
@@ -45,7 +83,7 @@ class Chef
45
83
  ui.color('unknown', :red)
46
84
  end
47
85
 
48
- flavor = s.respond_to?(:attributes) ? s.attributes["package"] : 'unknown'
86
+ flavor = s.package || 'unknown'
49
87
 
50
88
  servers << s.type
51
89
  servers << s.dataset
@@ -53,30 +91,47 @@ class Chef
53
91
  servers << s.ips.join(",")
54
92
  servers << "#{sprintf "%6.2f", s.memory/1024.0} GB"
55
93
  servers << "#{sprintf "%5.0f", s.disk/1024} GB"
56
- servers << pricing.monthly_formatted_price_for_flavor(flavor, price_column_width)
94
+ servers << pricing.format_monthly_price(flavor, PRICE_COLUMN_WIDTH)
57
95
 
58
- total_cost += (pricing[flavor] || 0)
96
+ servers << compute_node if show?(:compute_node)
97
+ servers << s.tags.map { |k, v| "#{k}:#{v}" }.join(' ') if (show?(:tags) && (s.tags rescue nil))
59
98
 
60
- if (s.tags rescue nil)
61
- servers << (show_tags? ? s.tags.map { |k, v| "#{k}:#{v}" }.join(' ') : "")
62
- else
63
- servers << "No Tags"
64
- end
99
+ total_monthly_price += pricing.monthly_price(flavor)
65
100
  end
66
101
 
67
- (columns - 3).times{servers << ""}
68
- servers << " Total"
69
- servers << pricing.formatted_price_for_value(total_cost * pricing.class::HOURS_PER_MONTH,
70
- price_column_width)
102
+ add_total_price(servers, total_monthly_price)
71
103
 
72
104
  puts ui.list(servers, :uneven_columns_across, columns)
73
105
  rescue => e
74
106
  output_error(e)
75
107
  end
76
108
 
77
- def show_tags?
78
- !ENV['SHOW_TAGS'].nil?
109
+ def add_total_price(servers, total_monthly_price)
110
+ 8.times { servers << "" }
111
+ servers << " Total"
112
+ servers << pricing.format_price(total_monthly_price, PRICE_COLUMN_WIDTH)
113
+ end
114
+
115
+ def show?(key)
116
+ (Chef::Config[:knife][:show] || []).include?(key)
79
117
  end
118
+
119
+ def num_of_extra_keys
120
+ ADDITIONAL_FIELDS.select{|k| show?(k)}.size
121
+ end
122
+
123
+ def sort_field
124
+ Chef::Config[:knife][:sort] || :name
125
+ end
126
+
127
+ def sort_by(matrix, field)
128
+ matrix[field.to_sym] || matrix[:name]
129
+ end
130
+
131
+ def pricing
132
+ @pricing ||= Joyent::Cloud::Pricing::Formatter.new(Joyent::Cloud::Pricing::Configuration.instance)
133
+ end
134
+
80
135
  end
81
136
  end
82
137
  end
@@ -0,0 +1,30 @@
1
+ require 'chef/knife/joyent_base'
2
+
3
+ class Chef
4
+ class Knife
5
+ class JoyentServerPricing < Knife
6
+
7
+ include Knife::JoyentBase
8
+
9
+ option :reserve_pricing,
10
+ :short => '-r <file>',
11
+ :long => '--reserve-pricing <file>',
12
+ :description => 'Apply reserve pricing to instances that qualify from a YAML file (see joyent-cloud-pricing gem)',
13
+ :proc => Proc.new { |key| Chef::Config[:knife][:reserve_pricing] = key }
14
+
15
+ banner "knife joyent server pricing [-r <reserve-pricing-configuration.yml> ]"
16
+
17
+ def run
18
+ flavors = []
19
+ self.connection.servers.each do |s|
20
+ flavor = s.package || 'unknown'
21
+ flavors << flavor
22
+ end
23
+ reporter = Joyent::Cloud::Pricing::Reporter.new(Chef::Config[:knife][:reserve_pricing], flavors)
24
+ puts reporter.render
25
+ rescue => e
26
+ output_error(e)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -1,3 +1,3 @@
1
1
  module KnifeJoyent
2
- VERSION = "0.4.5"
2
+ VERSION = "0.4.8"
3
3
  end
data/lib/knife-joyent.rb CHANGED
@@ -1,2 +1 @@
1
1
  require 'knife-joyent/version'
2
- require 'knife-joyent/pricing'
data/spec/spec_helper.rb CHANGED
@@ -8,7 +8,6 @@
8
8
  require 'knife-joyent'
9
9
 
10
10
  RSpec.configure do |config|
11
- config.treat_symbols_as_metadata_keys_with_true_values = true
12
11
  config.run_all_when_everything_filtered = true
13
12
  config.filter_run :focus
14
13
 
metadata CHANGED
@@ -1,32 +1,36 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: knife-joyent
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.5
4
+ version: 0.4.8
5
+ prerelease:
5
6
  platform: ruby
6
7
  authors:
7
8
  - Kevin Chan
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2014-03-04 00:00:00.000000000 Z
12
+ date: 2014-03-30 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: fog
15
16
  requirement: !ruby/object:Gem::Requirement
17
+ none: false
16
18
  requirements:
17
19
  - - ~>
18
20
  - !ruby/object:Gem::Version
19
- version: 1.15.0
21
+ version: 1.21.0
20
22
  type: :runtime
21
23
  prerelease: false
22
24
  version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
23
26
  requirements:
24
27
  - - ~>
25
28
  - !ruby/object:Gem::Version
26
- version: 1.15.0
29
+ version: 1.21.0
27
30
  - !ruby/object:Gem::Dependency
28
31
  name: multi_json
29
32
  requirement: !ruby/object:Gem::Requirement
33
+ none: false
30
34
  requirements:
31
35
  - - ~>
32
36
  - !ruby/object:Gem::Version
@@ -34,6 +38,7 @@ dependencies:
34
38
  type: :runtime
35
39
  prerelease: false
36
40
  version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
37
42
  requirements:
38
43
  - - ~>
39
44
  - !ruby/object:Gem::Version
@@ -41,43 +46,65 @@ dependencies:
41
46
  - !ruby/object:Gem::Dependency
42
47
  name: chef
43
48
  requirement: !ruby/object:Gem::Requirement
49
+ none: false
44
50
  requirements:
45
- - - '>='
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '11.6'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '11.6'
62
+ - !ruby/object:Gem::Dependency
63
+ name: joyent-cloud-pricing
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
46
68
  - !ruby/object:Gem::Version
47
- version: 0.10.10
69
+ version: 1.0.3
48
70
  type: :runtime
49
71
  prerelease: false
50
72
  version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
51
74
  requirements:
52
- - - '>='
75
+ - - ! '>='
53
76
  - !ruby/object:Gem::Version
54
- version: 0.10.10
77
+ version: 1.0.3
55
78
  - !ruby/object:Gem::Dependency
56
79
  name: rspec
57
80
  requirement: !ruby/object:Gem::Requirement
81
+ none: false
58
82
  requirements:
59
- - - '>='
83
+ - - ! '>='
60
84
  - !ruby/object:Gem::Version
61
85
  version: '0'
62
86
  type: :development
63
87
  prerelease: false
64
88
  version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
65
90
  requirements:
66
- - - '>='
91
+ - - ! '>='
67
92
  - !ruby/object:Gem::Version
68
93
  version: '0'
69
94
  - !ruby/object:Gem::Dependency
70
95
  name: guard-rspec
71
96
  requirement: !ruby/object:Gem::Requirement
97
+ none: false
72
98
  requirements:
73
- - - '>='
99
+ - - ! '>='
74
100
  - !ruby/object:Gem::Version
75
101
  version: '0'
76
102
  type: :development
77
103
  prerelease: false
78
104
  version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
79
106
  requirements:
80
- - - '>='
107
+ - - ! '>='
81
108
  - !ruby/object:Gem::Version
82
109
  version: '0'
83
110
  description: Joyent CloudAPI Support for Chef's Knife Command
@@ -119,6 +146,7 @@ files:
119
146
  - lib/chef/knife/joyent_server_list.rb
120
147
  - lib/chef/knife/joyent_server_metadata_delete.rb
121
148
  - lib/chef/knife/joyent_server_metadata_update.rb
149
+ - lib/chef/knife/joyent_server_pricing.rb
122
150
  - lib/chef/knife/joyent_server_reboot.rb
123
151
  - lib/chef/knife/joyent_server_resize.rb
124
152
  - lib/chef/knife/joyent_server_start.rb
@@ -131,35 +159,31 @@ files:
131
159
  - lib/chef/knife/joyent_tag_list.rb
132
160
  - lib/knife-joyent.rb
133
161
  - lib/knife-joyent/fw.rb
134
- - lib/knife-joyent/pricing.rb
135
- - lib/knife-joyent/pricing/config.rb
136
162
  - lib/knife-joyent/version.rb
137
- - spec/pricing/config_spec.rb
138
163
  - spec/spec_helper.rb
139
164
  homepage: https://github.com/kevinykchan/knife-joyent
140
165
  licenses: []
141
- metadata: {}
142
166
  post_install_message:
143
167
  rdoc_options: []
144
168
  require_paths:
145
169
  - lib
146
170
  required_ruby_version: !ruby/object:Gem::Requirement
171
+ none: false
147
172
  requirements:
148
- - - '>='
173
+ - - ! '>='
149
174
  - !ruby/object:Gem::Version
150
175
  version: '0'
151
176
  required_rubygems_version: !ruby/object:Gem::Requirement
177
+ none: false
152
178
  requirements:
153
- - - '>='
179
+ - - ! '>='
154
180
  - !ruby/object:Gem::Version
155
181
  version: '0'
156
182
  requirements: []
157
183
  rubyforge_project:
158
- rubygems_version: 2.0.5
184
+ rubygems_version: 1.8.23
159
185
  signing_key:
160
- specification_version: 4
186
+ specification_version: 3
161
187
  summary: Joyent CloudAPI Support for Chef's Knife Command
162
188
  test_files:
163
- - spec/pricing/config_spec.rb
164
189
  - spec/spec_helper.rb
165
- has_rdoc: true
checksums.yaml DELETED
@@ -1,7 +0,0 @@
1
- ---
2
- SHA1:
3
- metadata.gz: 06f8a691e8fcef6cd3a29f19b6c1e0b690b600ba
4
- data.tar.gz: 1b03a7c634502edc8a0d3b150772c71a3f342073
5
- SHA512:
6
- metadata.gz: 3e1780a45058aa5788c6076209a4e4ab4c87de2dfad622a10efd79a32208b2d8d6a3145cf1f1754a52e089eeef7015813d38877e986df743999cf7ef5c6f9bf3
7
- data.tar.gz: 71668684a9418d201ea0029f15f17879bf2a95eadcc89b7ff8986e28e9913b7ca0b1d426bc19c78e679e99916e0705827a928062fcf6c88efe9a1f041b419ece
@@ -1,62 +0,0 @@
1
- require 'open-uri'
2
- require 'nokogiri'
3
-
4
- module KnifeJoyent
5
- module Pricing
6
- class Config < ::Hash
7
- JOYENT_URL = "http://www.joyent.com/products/compute-service/pricing"
8
- HOURS_PER_MONTH = 720
9
-
10
- def initialize
11
- super
12
- from_uri
13
- end
14
-
15
- def from_uri(uri = JOYENT_URL)
16
- parse_html_document Nokogiri::HTML(open(uri))
17
- rescue
18
- end
19
-
20
- def from_html_file filename
21
- parse_html_document Nokogiri::HTML(File.read(filename))
22
- end
23
-
24
- def monthly_price_for_flavor(flavor_name)
25
- self[flavor_name] ? sprintf("$%.2f", self[flavor_name] * HOURS_PER_MONTH) : ""
26
- end
27
-
28
- def monthly_formatted_price_for_flavor(flavor, width = 10)
29
- self[flavor] ? formatted_price_for_value(self[flavor] * HOURS_PER_MONTH, width) : ""
30
- end
31
-
32
- def formatted_price_for_value(value, width = 10)
33
- sprintf("%#{width}s", currency_format(sprintf("$%.2f", value)))
34
- end
35
-
36
- # Returns string formatted with commas in the middle, such as "9,999,999"
37
- def currency_format string
38
- while string.sub!(/(\d+)(\d\d\d)/,'\1,\2'); end
39
- string
40
- end
41
-
42
- private
43
-
44
- def parse_html_document doc
45
- mappings = Hash.new
46
- specs = doc.css("ul.full-specs")
47
- specs.each do |ul|
48
- lis = ul.css("span").map(&:content)
49
- # grab last two <li> elements in each <ul class="full-spec"> block
50
- os, cost, flavor = lis[-3], lis[-2].gsub(/^\$/, ''), lis[-1]
51
- next if cost == "N/A"
52
- next if flavor =~ /kvm/ && os !~ /linux/i
53
-
54
- mappings[flavor] = cost.to_f
55
- end
56
-
57
- self.merge! mappings
58
- end
59
- end
60
- end
61
- end
62
-
@@ -1 +0,0 @@
1
- require_relative 'pricing/config'
@@ -1,42 +0,0 @@
1
- require_relative '../spec_helper'
2
-
3
-
4
- describe KnifeJoyent::Pricing::Config do
5
- let(:config) { KnifeJoyent::Pricing::Config.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
- def verify
12
- prices.keys.each do |flavor|
13
- config[flavor].should eql(prices[flavor])
14
- end
15
- end
16
-
17
- it "should load pricing configuration hash from Joyent Website" do
18
- config.from_uri
19
- verify
20
- end
21
-
22
- context "#monthly_price_for_flavor" do
23
- it "should return properly formatted monthly price" do
24
- expect(config.monthly_price_for_flavor "g3-standard-0.625-smartos").to eql("$14.40")
25
- expect(config.monthly_price_for_flavor "g3-standard-30-kvm").to eql("$691.20")
26
- end
27
- end
28
- context "#monthly_formatted_price_for_flavor" do
29
- it "should return properly formatted monthly price" do
30
- expect(config.monthly_formatted_price_for_flavor "g3-standard-48-smartos").to eql(" $1,105.92")
31
- end
32
- it "should return blank when no match was found" do
33
- expect(config.monthly_formatted_price_for_flavor "asdfkasdfasdlfkjasl;dkjf").to eql("")
34
- end
35
- end
36
- context "#formatted_price_for_value" do
37
- it "should return properly formatted price" do
38
- expect(config.formatted_price_for_value 24566.34).to eql("$24,566.34")
39
- expect(config.formatted_price_for_value 4566.34).to eql(" $4,566.34")
40
- end
41
- end
42
- end