ztk 1.6.2 → 1.6.3

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/Gemfile CHANGED
@@ -1,5 +1,6 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  gem "json" if (RUBY_VERSION == "1.8.7")
4
+ gem "activesupport", "< 4.0.0" if (RUBY_VERSION < "1.9.3")
4
5
 
5
6
  gemspec
data/Rakefile CHANGED
@@ -39,7 +39,7 @@ require 'yard'
39
39
  require 'yard/rake/yardoc_task'
40
40
 
41
41
  GEM_NAME = File.basename(Dir.pwd)
42
- DOC_PATH = File.expand_path(File.join("..", "/", "#{GEM_NAME}.doc"))
42
+ DOC_PATH = File.expand_path(File.join("..", "", "#{GEM_NAME}.doc"))
43
43
 
44
44
  namespace :doc do
45
45
  YARD::Rake::YardocTask.new(:pages) do |t|
@@ -1,7 +1,3 @@
1
- require 'ostruct'
2
- require 'timeout'
3
- require 'socket'
4
-
5
1
  module ZTK
6
2
 
7
3
  # Command Error Class
@@ -22,6 +18,18 @@ module ZTK
22
18
  #
23
19
  # @author Zachary Patten <zachary AT jovelabs DOT com>
24
20
  class Command < ZTK::Base
21
+ require 'ostruct'
22
+ require 'timeout'
23
+ require 'socket'
24
+
25
+ require 'ztk/command/download'
26
+ require 'ztk/command/exec'
27
+ require 'ztk/command/private'
28
+ require 'ztk/command/upload'
29
+
30
+ include ZTK::Command::Download
31
+ include ZTK::Command::Exec
32
+ include ZTK::Command::Upload
25
33
 
26
34
  # @param [Hash] configuration Sets the overall default configuration for the
27
35
  # class. For example, all calls to *exec* against this instance will use
@@ -46,168 +54,14 @@ module ZTK
46
54
  :exit_code => 0,
47
55
  :silence => false
48
56
  }.merge(configuration))
49
- config.ui.logger.debug { "config=#{config.send(:table).inspect}" }
50
- end
51
-
52
- # Execute Command
53
- #
54
- # @example Execute a command:
55
- # cmd = ZTK::Command.new(:silence => true)
56
- # puts cmd.exec("hostname", :silence => false).inspect
57
- #
58
- # @param [String] command The command to execute.
59
- # @param [Hash] options The options hash for executing the command.
60
- # @option options [Integer] :timeout (600) How long in seconds before
61
- # the command will timeout.
62
- # @option options [Boolean] :ignore_exit_status (false) Whether or not
63
- # we should ignore the exit status of the the process we spawn. By
64
- # default we do not ignore the exit status and throw an exception if it is
65
- # non-zero.
66
- # @option options [Integer] :exit_code (0) The exit code we expect the
67
- # process to return. This is ignore if *ignore_exit_status* is true.
68
- # @option options [Boolean] :silence (false) Whether or not we should
69
- # squelch the output of the process. The output will always go to the
70
- # logging device supplied in the ZTK::UI object. The output is always
71
- # available in the return value from the method additionally.
72
- #
73
- # @return [OpenStruct#output] The output of the command, both STDOUT and
74
- # STDERR combined.
75
- # @return [OpenStruct#exit_code] The exit code of the process.
76
- def exec(command, options={})
77
- options = OpenStruct.new(config.send(:table).merge(options))
78
-
79
- options.ui.logger.debug { "config=#{options.send(:table).inspect}" }
80
- options.ui.logger.debug { "options=#{options.send(:table).inspect}" }
81
- options.ui.logger.info { "command(#{command.inspect})" }
82
-
83
- if options.replace_current_process
84
- options.ui.logger.fatal { "REPLACING CURRENT PROCESS - GOODBYE!" }
85
- Kernel.exec(command)
86
- end
87
-
88
- output = ""
89
- exit_code = -1
90
- stdout_header = false
91
- stderr_header = false
92
-
93
- parent_stdout_reader, child_stdout_writer = IO.pipe
94
- parent_stderr_reader, child_stderr_writer = IO.pipe
95
-
96
- start_time = Time.now.utc
97
-
98
- pid = Process.fork do
99
- parent_stdout_reader.close
100
- parent_stderr_reader.close
101
-
102
- STDOUT.reopen(child_stdout_writer)
103
- STDERR.reopen(child_stderr_writer)
104
- STDIN.reopen("/dev/null")
105
-
106
- child_stdout_writer.close
107
- child_stderr_writer.close
108
57
 
109
- Kernel.exec(command)
110
- end
111
- child_stdout_writer.close
112
- child_stderr_writer.close
113
-
114
- reader_writer_key = {parent_stdout_reader => :stdout, parent_stderr_reader => :stderr}
115
- reader_writer_map = {parent_stdout_reader => options.ui.stdout, parent_stderr_reader => options.ui.stderr}
116
-
117
- direct_log(:info) { log_header("COMMAND") }
118
- direct_log(:info) { "#{command.inspect}\n" }
119
- direct_log(:info) { log_header("STARTED") }
120
-
121
- begin
122
- Timeout.timeout(options.timeout) do
123
- loop do
124
- pipes = IO.select(reader_writer_map.keys, [], reader_writer_map.keys).first
125
- pipes.each do |pipe|
126
- data = pipe.read
127
-
128
- if (data.nil? || data.empty?)
129
- sleep(0.1)
130
- next
131
- end
132
-
133
- case reader_writer_key[pipe]
134
- when :stdout then
135
- if !stdout_header
136
- direct_log(:info) { log_header("STDOUT") }
137
- stdout_header = true
138
- stderr_header = false
139
- end
140
- reader_writer_map[pipe].write(data) unless options.silence
141
- direct_log(:info) { data }
142
-
143
- when :stderr then
144
- if !stderr_header
145
- direct_log(:warn) { log_header("STDERR") }
146
- stderr_header = true
147
- stdout_header = false
148
- end
149
- reader_writer_map[pipe].write(data) unless options.silence
150
- direct_log(:warn) { data }
151
- end
152
-
153
- output += data
154
-
155
- options.on_progress.nil? or options.on_progress.call
156
- end
157
-
158
- break if reader_writer_map.keys.all?{ |reader| reader.eof? }
159
- end
160
- end
161
- rescue Timeout::Error => e
162
- direct_log(:fatal) { log_header("TIMEOUT") }
163
- log_and_raise(CommandError, "Process timed out after #{options.timeout} seconds!")
164
- end
165
-
166
- Process.waitpid(pid)
167
- exit_code = $?.exitstatus
168
- direct_log(:info) { log_header("STOPPED") }
169
-
170
- parent_stdout_reader.close
171
- parent_stderr_reader.close
172
-
173
- options.ui.logger.debug { "exit_code(#{exit_code})" }
174
-
175
- if !options.ignore_exit_status && (exit_code != options.exit_code)
176
- log_and_raise(CommandError, "exec(#{command.inspect}, #{options.inspect}) failed! [#{exit_code}]")
177
- end
178
- OpenStruct.new(:command => command, :output => output, :exit_code => exit_code)
179
- end
180
-
181
- # Not Supported
182
- # @raise [CommandError] Not Supported
183
- def upload(*args)
184
- log_and_raise(CommandError, "Not Supported")
185
- end
186
-
187
- # Not Supported
188
- # @raise [CommandError] Not Supported
189
- def download(*args)
190
- log_and_raise(CommandError, "Not Supported")
58
+ config.ui.logger.debug { "config=#{config.send(:table).inspect}" }
191
59
  end
192
60
 
193
61
 
194
62
  private
195
63
 
196
- # Returns a string in the format of "user@hostname" for the current
197
- # shell.
198
- def tag
199
- @@hostname ||= Socket.gethostname.split('.').first.strip
200
- "#{ENV['USER']}@#{@@hostname}"
201
- end
202
-
203
- # Formats a header suitable for writing to the direct logger when logging
204
- # sessions.
205
- def log_header(what)
206
- count = 8
207
- sep = ("=" * count)
208
- header = [sep, "[ #{what} ]", sep, "[ #{tag} ]", sep, "[ #{what} ]", sep].join
209
- "#{header}\n"
210
- end
64
+ include ZTK::Command::Private
211
65
 
212
66
  end
213
67
 
@@ -0,0 +1,16 @@
1
+ module ZTK
2
+ class Command
3
+
4
+ # Command Download Functionality
5
+ module Download
6
+
7
+ # Not Supported
8
+ # @raise [CommandError] Not Supported
9
+ def download(*args)
10
+ log_and_raise(CommandError, "Not Supported")
11
+ end
12
+
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,139 @@
1
+ module ZTK
2
+ class Command
3
+
4
+ # Command Exec Functionality
5
+ module Exec
6
+
7
+ # Execute Command
8
+ #
9
+ # @example Execute a command:
10
+ # cmd = ZTK::Command.new(:silence => true)
11
+ # puts cmd.exec("hostname", :silence => false).inspect
12
+ #
13
+ # @param [String] command The command to execute.
14
+ # @param [Hash] options The options hash for executing the command.
15
+ # @option options [Integer] :timeout (600) How long in seconds before
16
+ # the command will timeout.
17
+ # @option options [Boolean] :ignore_exit_status (false) Whether or not
18
+ # we should ignore the exit status of the the process we spawn. By
19
+ # default we do not ignore the exit status and throw an exception if it is
20
+ # non-zero.
21
+ # @option options [Integer] :exit_code (0) The exit code we expect the
22
+ # process to return. This is ignore if *ignore_exit_status* is true.
23
+ # @option options [Boolean] :silence (false) Whether or not we should
24
+ # squelch the output of the process. The output will always go to the
25
+ # logging device supplied in the ZTK::UI object. The output is always
26
+ # available in the return value from the method additionally.
27
+ #
28
+ # @return [OpenStruct#output] The output of the command, both STDOUT and
29
+ # STDERR combined.
30
+ # @return [OpenStruct#exit_code] The exit code of the process.
31
+ def exec(command, options={})
32
+ options = OpenStruct.new(config.send(:table).merge(options))
33
+
34
+ options.ui.logger.debug { "config=#{options.send(:table).inspect}" }
35
+ options.ui.logger.debug { "options=#{options.send(:table).inspect}" }
36
+ options.ui.logger.info { "command(#{command.inspect})" }
37
+
38
+ if options.replace_current_process
39
+ options.ui.logger.fatal { "REPLACING CURRENT PROCESS - GOODBYE!" }
40
+ Kernel.exec(command)
41
+ end
42
+
43
+ output = ""
44
+ exit_code = -1
45
+ stdout_header = false
46
+ stderr_header = false
47
+
48
+ parent_stdout_reader, child_stdout_writer = IO.pipe
49
+ parent_stderr_reader, child_stderr_writer = IO.pipe
50
+
51
+ start_time = Time.now.utc
52
+
53
+ pid = Process.fork do
54
+ parent_stdout_reader.close
55
+ parent_stderr_reader.close
56
+
57
+ STDOUT.reopen(child_stdout_writer)
58
+ STDERR.reopen(child_stderr_writer)
59
+ STDIN.reopen("/dev/null")
60
+
61
+ child_stdout_writer.close
62
+ child_stderr_writer.close
63
+
64
+ Kernel.exec(command)
65
+ end
66
+ child_stdout_writer.close
67
+ child_stderr_writer.close
68
+
69
+ reader_writer_key = {parent_stdout_reader => :stdout, parent_stderr_reader => :stderr}
70
+ reader_writer_map = {parent_stdout_reader => options.ui.stdout, parent_stderr_reader => options.ui.stderr}
71
+
72
+ direct_log(:info) { log_header("COMMAND") }
73
+ direct_log(:info) { "#{command.inspect}\n" }
74
+ direct_log(:info) { log_header("STARTED") }
75
+
76
+ begin
77
+ Timeout.timeout(options.timeout) do
78
+ loop do
79
+ pipes = IO.select(reader_writer_map.keys, [], reader_writer_map.keys).first
80
+ pipes.each do |pipe|
81
+ data = pipe.read
82
+
83
+ if (data.nil? || data.empty?)
84
+ sleep(0.1)
85
+ next
86
+ end
87
+
88
+ case reader_writer_key[pipe]
89
+ when :stdout then
90
+ if !stdout_header
91
+ direct_log(:info) { log_header("STDOUT") }
92
+ stdout_header = true
93
+ stderr_header = false
94
+ end
95
+ reader_writer_map[pipe].write(data) unless options.silence
96
+ direct_log(:info) { data }
97
+
98
+ when :stderr then
99
+ if !stderr_header
100
+ direct_log(:warn) { log_header("STDERR") }
101
+ stderr_header = true
102
+ stdout_header = false
103
+ end
104
+ reader_writer_map[pipe].write(data) unless options.silence
105
+ direct_log(:warn) { data }
106
+ end
107
+
108
+ output += data
109
+
110
+ options.on_progress.nil? or options.on_progress.call
111
+ end
112
+
113
+ break if reader_writer_map.keys.all?{ |reader| reader.eof? }
114
+ end
115
+ end
116
+ rescue Timeout::Error => e
117
+ direct_log(:fatal) { log_header("TIMEOUT") }
118
+ log_and_raise(CommandError, "Process timed out after #{options.timeout} seconds!")
119
+ end
120
+
121
+ Process.waitpid(pid)
122
+ exit_code = $?.exitstatus
123
+ direct_log(:info) { log_header("STOPPED") }
124
+
125
+ parent_stdout_reader.close
126
+ parent_stderr_reader.close
127
+
128
+ options.ui.logger.debug { "exit_code(#{exit_code})" }
129
+
130
+ if !options.ignore_exit_status && (exit_code != options.exit_code)
131
+ log_and_raise(CommandError, "exec(#{command.inspect}, #{options.inspect}) failed! [#{exit_code}]")
132
+ end
133
+ OpenStruct.new(:command => command, :output => output, :exit_code => exit_code)
134
+ end
135
+
136
+ end
137
+
138
+ end
139
+ end
@@ -0,0 +1,26 @@
1
+ module ZTK
2
+ class Command
3
+
4
+ # Command Private Functionality
5
+ module Private
6
+
7
+ # Returns a string in the format of "user@hostname" for the current
8
+ # shell.
9
+ def tag
10
+ @@hostname ||= Socket.gethostname.split('.').first.strip
11
+ "#{ENV['USER']}@#{@@hostname}"
12
+ end
13
+
14
+ # Formats a header suitable for writing to the direct logger when logging
15
+ # sessions.
16
+ def log_header(what)
17
+ count = 8
18
+ sep = ("=" * count)
19
+ header = [sep, "[ #{what} ]", sep, "[ #{tag} ]", sep, "[ #{what} ]", sep].join
20
+ "#{header}\n"
21
+ end
22
+
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,16 @@
1
+ module ZTK
2
+ class Command
3
+
4
+ # Command Upload Functionality
5
+ module Upload
6
+
7
+ # Not Supported
8
+ # @raise [CommandError] Not Supported
9
+ def upload(*args)
10
+ log_and_raise(CommandError, "Not Supported")
11
+ end
12
+
13
+ end
14
+
15
+ end
16
+ end
@@ -31,6 +31,19 @@ module ZTK
31
31
  raise LocatorError, "Could not locate '#{File.join(args)}'!"
32
32
  end
33
33
 
34
+ # Returns the root for the filesystem we are operating on. Ignores
35
+ # mount boundries on *nix.
36
+ #
37
+ # For all flavors of *nix this should always return "/".
38
+ #
39
+ # Windows should expect something similar to "C:\".
40
+ #
41
+ # @return [String] The root path of the file-system. For unix this should
42
+ # always be "/". For windows this should be something like "C:\".
43
+ def root
44
+ Dir.pwd.split(File::SEPARATOR).first
45
+ end
46
+
34
47
  end
35
48
 
36
49
  end
@@ -1,6 +1,3 @@
1
- require 'socket'
2
- require 'timeout'
3
-
4
1
  module ZTK
5
2
 
6
3
  # Report Error Class
@@ -19,169 +16,28 @@ module ZTK
19
16
  #
20
17
  # @author Zachary Patten <zachary AT jovelabs DOT com>
21
18
  class Report < ZTK::Base
19
+ require 'socket'
20
+ require 'timeout'
21
+
22
+ require 'ztk/report/list'
23
+ require 'ztk/report/private'
24
+ require 'ztk/report/spreadsheet'
25
+
26
+ include ZTK::Report::List
27
+ include ZTK::Report::Spreadsheet
22
28
 
23
29
  # @param [Hash] configuration Configuration options hash.
24
30
  def initialize(configuration={})
25
31
  super({
26
32
  }.merge(configuration))
27
- config.ui.logger.debug { "config=#{config.send(:table).inspect}" }
28
- end
29
-
30
- # Displays data in a spreadsheet style.
31
- #
32
- # +-------------+-------+-------+--------+----------------+-------------------+--------------+---------+
33
- # | NAME | ALIVE | ARCH | DISTRO | IP | MAC | CHEF VERSION | PERSIST |
34
- # +-------------+-------+-------+--------+----------------+-------------------+--------------+---------+
35
- # | sudo | false | amd64 | ubuntu | 192.168.99.110 | 00:00:5e:34:d6:aa | N/A | true |
36
- # | timezone | false | amd64 | ubuntu | 192.168.122.47 | 00:00:5e:92:d7:f6 | N/A | true |
37
- # | chef-client | false | amd64 | ubuntu | 192.168.159.98 | 00:00:5e:c7:ce:26 | N/A | true |
38
- # | users | false | amd64 | ubuntu | 192.168.7.78 | 00:00:5e:89:f9:50 | N/A | true |
39
- # +-------------+-------+-------+--------+----------------+-------------------+--------------+---------+
40
- #
41
- # @param [Array<Object>,Object] dataset A single object or an array of
42
- # objects for which we want to generate a report
43
- # @param [Array] headers An array of headers used for ordering the output.
44
- # @return [OpenStruct]
45
- def spreadsheet(dataset, headers, &block)
46
- !block_given? and log_and_raise(ReportError, "You must supply a block!")
47
- headers.nil? and log_and_raise(ReportError, "Headers can not be nil!")
48
- dataset.nil? and log_and_raise(ReportError, "Dataset can not be nil!")
49
-
50
- rows = Array.new
51
- max_lengths = OpenStruct.new
52
- headers = headers.map(&:to_s).map(&:downcase).map(&:to_sym)
53
-
54
- if dataset.is_a?(Array)
55
- dataset.each do |data|
56
- rows << block.call(data)
57
- end
58
- else
59
- rows << block.call(dataset)
60
- end
61
- rows.compact!
62
-
63
- if rows.count > 0
64
- max_lengths = max_spreadsheet_lengths(headers, rows)
65
- header_line = headers.collect { |header| "%-#{max_lengths.send(:table)[header]}s" % header.to_s.upcase }
66
- header_line = format_row(header_line)
67
-
68
- config.ui.stdout.puts(format_header(headers, max_lengths))
69
- config.ui.stdout.puts(header_line)
70
- config.ui.stdout.puts(format_header(headers, max_lengths))
71
-
72
- rows.each do |row|
73
- row_line = headers.collect do |header|
74
- header_length = max_lengths.send(:table)[header]
75
- content = row.send(:table)[header]
76
-
77
- "%-#{header_length}s" % content
78
- end
79
-
80
- row_line = format_row(row_line)
81
- config.ui.stdout.puts(row_line)
82
- end
83
-
84
- config.ui.stdout.puts(format_header(headers, max_lengths))
85
- OpenStruct.new(:rows => rows, :max_lengths => max_lengths, :width => calculate_spreadsheet_width(headers, max_lengths))
86
- else
87
- OpenStruct.new(:rows => rows, :max_lengths => 0, :width => 0)
88
- end
89
-
90
- end
91
-
92
- # Displays data in a key-value list style.
93
- #
94
- # +-------------------------------------------------------------------+
95
- # | PROVIDER: Cucumber::Chef::Provider::Vagrant |
96
- # | ID: default |
97
- # | STATE: aborted |
98
- # | USERNAME: vagrant |
99
- # | IP ADDRESS: 127.0.0.1 |
100
- # | PORT: 2222 |
101
- # | CHEF-SERVER API: http://127.0.0.1:4000 |
102
- # | CHEF-SERVER WEBUI: http://127.0.0.1:4040 |
103
- # | CHEF-SERVER DEFAULT USER: admin |
104
- # | CHEF-SERVER DEFAULT PASSWORD: p@ssw0rd1 |
105
- # +-------------------------------------------------------------------+
106
- #
107
- # @param [Array<Object>,Object] dataset A single object or an array of
108
- # objects for which we want to generate a report
109
- # @param [Array] headers An array of headers used for ordering the output.
110
- # @return [OpenStruct]
111
- def list(dataset, headers, &block)
112
- !block_given? and log_and_raise(ReportError, "You must supply a block!")
113
- headers.nil? and log_and_raise(ReportError, "Headers can not be nil!")
114
- dataset.nil? and log_and_raise(ReportError, "Dataset can not be nil!")
115
33
 
116
- rows = Array.new
117
- max_lengths = OpenStruct.new
118
- headers = headers.map(&:to_s).map(&:downcase).map(&:to_sym)
119
-
120
- if dataset.is_a?(Array)
121
- dataset.each do |data|
122
- rows << block.call(data)
123
- end
124
- else
125
- rows << block.call(dataset)
126
- end
127
- rows.compact!
128
-
129
- if rows.count > 0
130
- max_key_length = headers.collect{ |header| header.to_s.length }.max
131
- max_value_length = rows.collect{ |row| headers.collect{ |header| row.send(:table)[header].to_s.length }.max }.max
132
-
133
- width = (max_key_length + max_value_length + 2 + 2 + 2)
134
-
135
- rows.each do |row|
136
- config.ui.stdout.puts("+#{"-" * width}+")
137
- headers.each do |header|
138
- entry_line = format_entry(header, max_key_length, row.send(:table)[header], max_value_length)
139
- config.ui.stdout.puts(entry_line)
140
- end
141
- end
142
- config.ui.stdout.puts("+#{"-" * width}+")
143
- OpenStruct.new(:rows => rows, :max_key_length => max_key_length, :max_value_length => max_value_length, :width => width)
144
- else
145
- OpenStruct.new(:rows => rows, :max_key_length => 0, :max_value_length => 0, :width => 0)
146
- end
34
+ config.ui.logger.debug { "config=#{config.send(:table).inspect}" }
147
35
  end
148
36
 
149
37
 
150
38
  private
151
39
 
152
- def max_spreadsheet_lengths(headers, rows)
153
- max_lengths = OpenStruct.new
154
- headers.each do |header|
155
- collection = [header, rows.collect{|r| r.send(:table)[header] } ].flatten
156
- maximum = collection.map(&:to_s).map(&:length).max
157
- max_lengths.send(:table)[header] = maximum
158
- end
159
-
160
- max_lengths
161
- end
162
-
163
- def calculate_spreadsheet_width(headers, max_lengths)
164
- header_lengths = ((headers.count * 3) - 3)
165
- max_length = max_lengths.send(:table).values.reduce(:+)
166
- (2 + max_length + header_lengths + 2)
167
- end
168
-
169
- def format_header(headers, lengths)
170
- line = headers.collect do |header|
171
- "-" * lengths.send(:table)[header]
172
- end
173
-
174
- ["+-", line.join("-+-"), "-+"].join.strip
175
- end
176
-
177
- def format_row(*args)
178
- spacer = " "
179
- [spacer, args, spacer].flatten.join(" | ").strip
180
- end
181
-
182
- def format_entry(key, key_length, value, value_length)
183
- "| %#{key_length}s: %-#{value_length}s |" % [key.to_s.upcase, value.to_s]
184
- end
40
+ include ZTK::Report::Private
185
41
 
186
42
  end
187
43