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.
- data/features/nodes.feature +15 -0
- data/features/step_definitions/chef_steps.rb +14 -2
- data/features/step_definitions/init_steps.rb +9 -0
- data/lib/iv-cli.rb +4 -2
- data/lib/iv-cli/app.rb +3 -0
- data/lib/iv-cli/command.rb +17 -28
- data/lib/iv-cli/commands/init.rb +2 -2
- data/lib/iv-cli/commands/nodes.rb +15 -45
- data/lib/iv-cli/formatter.rb +13 -0
- data/lib/iv-cli/formatters/base.rb +24 -0
- data/lib/iv-cli/formatters/csv.rb +20 -0
- data/lib/iv-cli/formatters/table.rb +54 -0
- data/lib/iv-cli/helpers/errors.rb +19 -6
- data/lib/iv-cli/helpers/subclass_registration.rb +37 -0
- data/lib/iv-cli/version.rb +1 -1
- metadata +7 -2
data/features/nodes.feature
CHANGED
@@ -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
|
-
#{
|
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
|
data/lib/iv-cli.rb
CHANGED
@@ -3,5 +3,7 @@ module IV
|
|
3
3
|
end
|
4
4
|
end
|
5
5
|
|
6
|
-
|
7
|
-
|
6
|
+
$:.unshift(File.dirname(__FILE__))
|
7
|
+
|
8
|
+
require 'iv-cli/config'
|
9
|
+
require 'iv-cli/app'
|
data/lib/iv-cli/app.rb
CHANGED
data/lib/iv-cli/command.rb
CHANGED
@@ -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 :
|
12
|
+
attr_reader :iv_config
|
8
13
|
|
9
14
|
def self.run_command args, options = {}
|
10
|
-
|
15
|
+
subclass_class(args.shift).new.run(args, options)
|
11
16
|
end
|
12
17
|
|
13
18
|
def self.invalid_command? command
|
14
|
-
!
|
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.
|
29
|
+
def self.config_file
|
37
30
|
"#{config_dir}/config.yml"
|
38
31
|
end
|
39
32
|
|
40
|
-
def
|
41
|
-
unless ::File.exists?(self.class.
|
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
|
-
@
|
37
|
+
@iv_config = ::YAML.load_file(self.class.config_file)
|
45
38
|
end
|
46
39
|
|
47
40
|
def ensure_opscode_config
|
48
|
-
|
49
|
-
unless
|
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'
|
data/lib/iv-cli/commands/init.rb
CHANGED
@@ -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.
|
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.
|
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
|
-
|
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
|
-
|
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
|
35
|
+
node_info << translate_node_status(node, org)
|
26
36
|
if node_info.length%30 == 0
|
27
|
-
|
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
|
-
|
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
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
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
|
+
|
data/lib/iv-cli/version.rb
CHANGED
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.
|
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-
|
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
|