ztk 1.6.2 → 1.6.3

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