iv-cli 0.0.5 → 0.0.6

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.
@@ -81,3 +81,18 @@ Feature: User can interact with opscode nodes
81
81
  Unable to read opscode account info from config.
82
82
  """
83
83
  And the exit status should be 4
84
+
85
+ Scenario: User can specify a CSV formatter for node lists
86
+ Given a default iv configuration file for "demo"
87
+ And the following nodes:
88
+ | status | name | fqdn | ip | os |
89
+ | 1 minute ago | host.1 | host.1 | 10.10.10.10 | solaris2 |
90
+ | 2 minutes ago | host.2 | host.2 | 10.10.10.11 | solaris2 |
91
+ When I run `iv nodes --formatter csv`
92
+ Then the stderr should contain ""
93
+ And the exit status should be 0
94
+ And the stdout should contain:
95
+ """
96
+ 1 minute ago,host.1,10.10.10.10,solaris2,demo
97
+ 2 minutes ago,host.2,10.10.10.11,solaris2,demo
98
+ """
@@ -1,10 +1,22 @@
1
+
2
+ def node_response(table)
3
+ table.rows.map{|r| r.join(', ')}.join("\n")
4
+ end
5
+
1
6
  Given /^I expect Chef to return the following nodes for "([^"]*)", with user "([^"]*)" and key "([^"]*)":$/ do |organization, user, key, table|
2
- nodes = table.rows.map{|r| r.join(', ')}.join("\n")
3
7
  steps %{
4
8
  Given I double `knife status -s https://api.opscode.com/organizations/#{organization} -u #{user} -k #{key} --no-color` with exit status 0 and stdout:
5
9
  """
6
- #{nodes}
10
+ #{node_response(table)}
7
11
  """
8
12
  }
9
13
  end
10
14
 
15
+ Given /^the following nodes:$/i do |table|
16
+ steps %{
17
+ Given I double `knife status -s https://api.opscode.com/organizations/acct -u user -k key --no-color` with exit status 0 and stdout:
18
+ """
19
+ #{node_response(table)}
20
+ """
21
+ }
22
+ end
@@ -34,3 +34,12 @@ Given /^the following iv config file:$/i do |contents|
34
34
  file.puts contents
35
35
  end
36
36
  end
37
+
38
+ Given /^a default iv configuration file for "([^"]+)"$/i do |identifier|
39
+ steps %{
40
+ Given a directory named "tmp/test/.iv"
41
+ }
42
+ ::File.open("tmp/test/.iv/config.yml","w") do |file|
43
+ file.puts "---\nopscode:\n #{identifier}:\n acct: acct\n user: user\n key: key\n"
44
+ end
45
+ end
@@ -3,5 +3,7 @@ module IV
3
3
  end
4
4
  end
5
5
 
6
- Dir["#{File.dirname(__FILE__)}/iv-cli/helpers/*.rb"].sort.each { |f| require f }
7
- Dir["#{File.dirname(__FILE__)}/iv-cli/**/*.rb"].sort.each { |f| require f }
6
+ $:.unshift(File.dirname(__FILE__))
7
+
8
+ require 'iv-cli/config'
9
+ require 'iv-cli/app'
@@ -1,5 +1,8 @@
1
1
  require 'mixlib/cli'
2
2
 
3
+ require 'iv-cli/helpers/errors'
4
+ require 'iv-cli/command'
5
+
3
6
  class IV::CLI::App
4
7
  include Mixlib::CLI
5
8
  include IV::CLI::Helpers::Errors
@@ -1,60 +1,49 @@
1
+ require 'mixlib/cli'
1
2
  require 'yaml'
2
3
 
4
+ require 'iv-cli/helpers/errors'
5
+ require 'iv-cli/helpers/subclass_registration'
6
+
3
7
  class IV::CLI::Command
4
8
  include Mixlib::CLI
5
9
  include IV::CLI::Helpers::Errors
10
+ include IV::CLI::Helpers::SubclassRegistration
6
11
 
7
- attr_reader :config
12
+ attr_reader :iv_config
8
13
 
9
14
  def self.run_command args, options = {}
10
- command_class(args.shift).new.run(args, options)
15
+ subclass_class(args.shift).new.run(args, options)
11
16
  end
12
17
 
13
18
  def self.invalid_command? command
14
- ! commands.include? command
19
+ ! subclasses.include? command
15
20
  end
16
21
 
17
22
 
18
23
  protected
19
24
 
20
- def self.commands
21
- @@commands ||= {}
22
- end
23
-
24
- def self.command_name
25
- name.split("::").last.downcase
26
- end
27
-
28
- def self.command_class(command)
29
- commands[command]
30
- end
31
-
32
25
  def self.config_dir
33
26
  "#{ENV['HOME']}/.iv"
34
27
  end
35
28
 
36
- def self.config
29
+ def self.config_file
37
30
  "#{config_dir}/config.yml"
38
31
  end
39
32
 
40
- def parse_config
41
- unless ::File.exists?(self.class.config)
33
+ def parse_iv_config
34
+ unless ::File.exists?(self.class.config_file)
42
35
  fail(4, %{Unable to find IV configuration.\nPlease run 'iv init' and update config.yml with your credentials})
43
36
  end
44
- @config = ::YAML.load_file(self.class.config)
37
+ @iv_config = ::YAML.load_file(self.class.config_file)
45
38
  end
46
39
 
47
40
  def ensure_opscode_config
48
- parse_config unless config
49
- unless config["opscode"]
41
+ parse_iv_config unless iv_config
42
+ unless iv_config["opscode"]
50
43
  fail(4, %{Unable to read opscode account info from config.})
51
44
  end
52
45
  end
53
-
54
- private
55
-
56
- def self.inherited(subclass)
57
- commands[subclass.command_name] = subclass
58
- end
59
-
60
46
  end
47
+
48
+ require 'iv-cli/commands/init'
49
+ require 'iv-cli/commands/nodes'
@@ -26,7 +26,7 @@ class IV::CLI::Command::Init < IV::CLI::Command
26
26
  ::FileUtils.mkdir_p(self.class.config_dir)
27
27
 
28
28
  puts "Generating config file ~/.iv/config.yml"
29
- ::File.open(self.class.config, "w") do |f|
29
+ ::File.open(self.class.config_file, "w") do |f|
30
30
  f.puts "---"
31
31
  write_default_opscode(f)
32
32
  end
@@ -35,7 +35,7 @@ class IV::CLI::Command::Init < IV::CLI::Command
35
35
  private
36
36
 
37
37
  def validate
38
- if !config[:force] && ::File.exists?(self.class.config)
38
+ if !config[:force] && ::File.exists?(self.class.config_file)
39
39
  STDERR.puts "Config file already exists at ~/.iv/config.yml"
40
40
  STDERR.puts "Use --force to overwrite file"
41
41
  Process.exit 10
@@ -1,14 +1,24 @@
1
1
  require 'pty'
2
2
 
3
+ require 'iv-cli/formatter'
4
+
3
5
  class IV::CLI::Command::Nodes < IV::CLI::Command
4
6
 
5
7
  banner "Usage: iv nodes [options]"
6
8
 
9
+ option :formatter,
10
+ :short => '-f FORMATTER',
11
+ :long => '--formatter FORMATTER',
12
+ :default => 'table',
13
+ :description => 'Output formatter'
14
+
7
15
  def run(args, options = {})
8
16
  parse_options(args)
9
- parse_config
17
+ parse_iv_config
10
18
  ensure_opscode_config
11
19
 
20
+ @formatter = IV::CLI::Formatter.find(config[:formatter]).new({:columns => [:status,:name,:ip,:os,:org]})
21
+
12
22
  stream_converge_status
13
23
  end
14
24
 
@@ -16,15 +26,15 @@ class IV::CLI::Command::Nodes < IV::CLI::Command
16
26
 
17
27
  def stream_converge_status
18
28
  node_info = []
19
- config["opscode"].each_pair do |org, settings|
29
+ iv_config["opscode"].each_pair do |org, settings|
20
30
  command = "knife status -s https://api.opscode.com/organizations/#{settings["acct"]} -u #{settings["user"]} -k #{settings["key"]} --no-color 2> /dev/null"
21
31
  begin
22
32
  PTY.spawn(command) do |stdin, stdout, pid|
23
33
  begin
24
34
  stdin.each do |node|
25
- node_info << translate_node_status(node.gsub(/\033\[[0-9;]*m/,''), org)
35
+ node_info << translate_node_status(node, org)
26
36
  if node_info.length%30 == 0
27
- output_node_info(node_info.slice!(0..29))
37
+ @formatter.output_rows(node_info.slice!(0..29))
28
38
  end
29
39
  end
30
40
  rescue Errno::EIO
@@ -33,51 +43,11 @@ class IV::CLI::Command::Nodes < IV::CLI::Command
33
43
  rescue PTY::ChildExited
34
44
  end
35
45
  end
36
- output_node_info(node_info)
46
+ @formatter.output_rows(node_info)
37
47
  end
38
48
 
39
49
  def translate_node_status(knife_output, org)
40
50
  node = knife_output.chomp.split(", ")
41
51
  {:status => node[0], :name => node[1], :fqdn => node[2], :ip => node[3], :os => node[4], :org => org}
42
52
  end
43
-
44
- def output_node_info(nodes)
45
- column_width = {}
46
- column_width[:status] = largest_size(nodes, :status)
47
- column_width[:name] = largest_size(nodes, :name)
48
- column_width[:ip] = largest_size(nodes, :ip)
49
- column_width[:os] = largest_size(nodes, :os)
50
- column_width[:org] = largest_size(nodes, :org)
51
-
52
- headers = []
53
- [:status, :name, :ip, :os, :org].each do |key|
54
- headers << sprintf(" %#{column_width[key]}s ", key.to_s)
55
- end
56
- print_row(headers, "+")
57
-
58
- nodes.each do |node|
59
- node_output = []
60
- [:status, :name, :ip, :os, :org].each do |key|
61
- node_output << sprintf(" %-#{column_width[key]}s ", node[key])
62
- end
63
- print_row(node_output)
64
- end
65
- STDOUT.flush
66
- end
67
-
68
- def largest_size(array, key)
69
- array.inject(0) do |largest, member|
70
- begin
71
- size = member[key].size; largest > size ? largest : size
72
- rescue NoMethodError
73
- largest
74
- end
75
- end
76
- end
77
-
78
- def print_row(array, separator = "|")
79
- STDOUT.print separator
80
- STDOUT.print array.join(separator)
81
- STDOUT.puts separator
82
- end
83
53
  end
@@ -0,0 +1,13 @@
1
+
2
+ module IV::CLI::Formatter
3
+
4
+ COLOR_REGEX = /\033\[[0-9;]*m/
5
+
6
+ def self.find(formatter_type)
7
+ IV::CLI::Formatter::Base.find(formatter_type)
8
+ end
9
+ end
10
+
11
+ require 'iv-cli/formatters/base'
12
+ require 'iv-cli/formatters/table'
13
+ require 'iv-cli/formatters/csv'
@@ -0,0 +1,24 @@
1
+
2
+ require 'iv-cli/helpers/errors'
3
+ require 'iv-cli/helpers/subclass_registration'
4
+
5
+ class IV::CLI::Formatter::Base
6
+ include IV::CLI::Helpers::Errors
7
+ include IV::CLI::Helpers::SubclassRegistration
8
+
9
+ attr_reader :options
10
+
11
+ def initialize(options = {})
12
+ @options = options
13
+ end
14
+
15
+ def self.find(formatter_type)
16
+ fail(4, %|Unable to find formatter for #{formatter_type}|) unless subclasses.include?(formatter_type)
17
+ subclasses[formatter_type]
18
+ end
19
+
20
+ def output_rows(*args)
21
+ #abstract method
22
+ raise "Abstract method called on IV::CLI::Formatter"
23
+ end
24
+ end
@@ -0,0 +1,20 @@
1
+
2
+ require 'iv-cli/formatter'
3
+ require 'iv-cli/formatters/base'
4
+
5
+ class IV::CLI::Formatter::CSV < IV::CLI::Formatter::Base
6
+
7
+ SEPARATOR = ','
8
+
9
+ def output_rows(list)
10
+ line = []
11
+ list.each do |row|
12
+ options[:columns].each do |column|
13
+ line << row[column]
14
+ end
15
+
16
+ STDOUT.puts line.join(SEPARATOR).gsub(IV::CLI::Formatter::COLOR_REGEX,'')
17
+ line.slice!(0..-1)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,54 @@
1
+
2
+ require 'iv-cli/formatter'
3
+ require 'iv-cli/formatters/base'
4
+
5
+ class IV::CLI::Formatter::Table < IV::CLI::Formatter::Base
6
+
7
+ def output_rows(list)
8
+ column_width = {}
9
+
10
+ # find column width by searching out widest member of each column
11
+ options[:columns].each do |column|
12
+ column_width[column] = largest_size(list, column)
13
+ end
14
+
15
+ # print row of headers every so often
16
+ headers = []
17
+ options[:columns].each do |key|
18
+ headers << sprintf(" %#{column_width[key]}s ", key.to_s)
19
+ end
20
+ print_row(headers, "+")
21
+
22
+ # print out each row
23
+ list.each do |member|
24
+ output = []
25
+ options[:columns].each do |key|
26
+ output << sprintf(" %-#{column_width[key]}s ", remove_color(member[key]))
27
+ end
28
+ print_row(output)
29
+ end
30
+ STDOUT.flush
31
+ end
32
+
33
+ private
34
+
35
+ def largest_size(array, key)
36
+ array.inject(0) do |largest, member|
37
+ begin
38
+ size = remove_color(member[key]).size; largest > size ? largest : size
39
+ rescue NoMethodError
40
+ largest
41
+ end
42
+ end
43
+ end
44
+
45
+ def print_row(array, separator = "|")
46
+ STDOUT.print separator
47
+ STDOUT.print array.join(separator)
48
+ STDOUT.puts separator
49
+ end
50
+
51
+ def remove_color(string)
52
+ string.gsub(IV::CLI::Formatter::COLOR_REGEX,'') rescue ''
53
+ end
54
+ end
@@ -1,9 +1,22 @@
1
- module IV::CLI::Helpers
2
- module Errors
3
- def fail(exit_status=1, message="")
4
- STDERR.puts "Error: #{message}" unless message.empty?
5
- STDERR.puts IV::CLI::USAGE
6
- exit exit_status
1
+ require 'iv-cli/usage'
2
+
3
+ module IV
4
+ module CLI
5
+ module Helpers
6
+ module Errors
7
+ module Methods
8
+ def fail(exit_status=1, message="")
9
+ STDERR.puts "Error: #{message}" unless message.empty?
10
+ STDERR.puts IV::CLI::USAGE
11
+ exit exit_status
12
+ end
13
+ end
14
+
15
+ def self.included(base)
16
+ base.send :include, Methods
17
+ base.extend Methods
18
+ end
19
+ end
7
20
  end
8
21
  end
9
22
  end
@@ -0,0 +1,37 @@
1
+
2
+ module IV
3
+ module CLI
4
+ module Helpers
5
+ module SubclassRegistration
6
+
7
+ def self.included(base)
8
+ base.extend ClassMethods
9
+ end
10
+
11
+ module ClassMethods
12
+
13
+ protected
14
+
15
+ def subclasses
16
+ @@subclasses ||= {}
17
+ end
18
+
19
+ def subclass_name
20
+ name.split("::").last.downcase
21
+ end
22
+
23
+ def subclass_class(subclass)
24
+ subclasses[subclass]
25
+ end
26
+
27
+ private
28
+
29
+ def inherited(subclass)
30
+ subclasses[subclass.subclass_name] = subclass
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+
@@ -1,5 +1,5 @@
1
1
  module IV
2
2
  module CLI
3
- VERSION = "0.0.5"
3
+ VERSION = "0.0.6"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: iv-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.6
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-05-25 00:00:00.000000000 Z
12
+ date: 2012-06-09 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
@@ -189,7 +189,12 @@ files:
189
189
  - lib/iv-cli/commands/init.rb
190
190
  - lib/iv-cli/commands/nodes.rb
191
191
  - lib/iv-cli/config.rb
192
+ - lib/iv-cli/formatter.rb
193
+ - lib/iv-cli/formatters/base.rb
194
+ - lib/iv-cli/formatters/csv.rb
195
+ - lib/iv-cli/formatters/table.rb
192
196
  - lib/iv-cli/helpers/errors.rb
197
+ - lib/iv-cli/helpers/subclass_registration.rb
193
198
  - lib/iv-cli/usage.rb
194
199
  - lib/iv-cli/version.rb
195
200
  - spec/lib/config_spec.rb