iv-cli 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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