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 +1 -0
- data/Rakefile +1 -1
- data/lib/ztk/command.rb +14 -160
- data/lib/ztk/command/download.rb +16 -0
- data/lib/ztk/command/exec.rb +139 -0
- data/lib/ztk/command/private.rb +26 -0
- data/lib/ztk/command/upload.rb +16 -0
- data/lib/ztk/locator.rb +13 -0
- data/lib/ztk/report.rb +11 -155
- data/lib/ztk/report/list.rb +67 -0
- data/lib/ztk/report/private.rb +44 -0
- data/lib/ztk/report/spreadsheet.rb +72 -0
- data/lib/ztk/ssh.rb +20 -114
- data/lib/ztk/ssh/bootstrap.rb +1 -1
- data/lib/ztk/ssh/console.rb +28 -0
- data/lib/ztk/ssh/core.rb +37 -0
- data/lib/ztk/ssh/download.rb +2 -2
- data/lib/ztk/ssh/file.rb +1 -1
- data/lib/ztk/ssh/private.rb +67 -0
- data/lib/ztk/ssh/upload.rb +2 -2
- data/lib/ztk/ui.rb +14 -0
- data/lib/ztk/version.rb +1 -1
- data/spec/ztk/logger_spec.rb +1 -1
- data/spec/ztk/ssh_spec.rb +8 -8
- metadata +18 -2
@@ -0,0 +1,67 @@
|
|
1
|
+
module ZTK
|
2
|
+
class Report
|
3
|
+
|
4
|
+
# Report List Functionality
|
5
|
+
module List
|
6
|
+
|
7
|
+
# Displays data in a key-value list style.
|
8
|
+
#
|
9
|
+
# +-------------------------------------------------------------------+
|
10
|
+
# | PROVIDER: Cucumber::Chef::Provider::Vagrant |
|
11
|
+
# | ID: default |
|
12
|
+
# | STATE: aborted |
|
13
|
+
# | USERNAME: vagrant |
|
14
|
+
# | IP ADDRESS: 127.0.0.1 |
|
15
|
+
# | PORT: 2222 |
|
16
|
+
# | CHEF-SERVER API: http://127.0.0.1:4000 |
|
17
|
+
# | CHEF-SERVER WEBUI: http://127.0.0.1:4040 |
|
18
|
+
# | CHEF-SERVER DEFAULT USER: admin |
|
19
|
+
# | CHEF-SERVER DEFAULT PASSWORD: p@ssw0rd1 |
|
20
|
+
# +-------------------------------------------------------------------+
|
21
|
+
#
|
22
|
+
# @param [Array<Object>,Object] dataset A single object or an array of
|
23
|
+
# objects for which we want to generate a report
|
24
|
+
# @param [Array] headers An array of headers used for ordering the output.
|
25
|
+
# @return [OpenStruct]
|
26
|
+
def list(dataset, headers, &block)
|
27
|
+
!block_given? and log_and_raise(ReportError, "You must supply a block!")
|
28
|
+
headers.nil? and log_and_raise(ReportError, "Headers can not be nil!")
|
29
|
+
dataset.nil? and log_and_raise(ReportError, "Dataset can not be nil!")
|
30
|
+
|
31
|
+
rows = Array.new
|
32
|
+
max_lengths = OpenStruct.new
|
33
|
+
headers = headers.map(&:to_s).map(&:downcase).map(&:to_sym)
|
34
|
+
|
35
|
+
if dataset.is_a?(Array)
|
36
|
+
dataset.each do |data|
|
37
|
+
rows << block.call(data)
|
38
|
+
end
|
39
|
+
else
|
40
|
+
rows << block.call(dataset)
|
41
|
+
end
|
42
|
+
rows.compact!
|
43
|
+
|
44
|
+
if rows.count > 0
|
45
|
+
max_key_length = headers.collect{ |header| header.to_s.length }.max
|
46
|
+
max_value_length = rows.collect{ |row| headers.collect{ |header| row.send(:table)[header].to_s.length }.max }.max
|
47
|
+
|
48
|
+
width = (max_key_length + max_value_length + 2 + 2 + 2)
|
49
|
+
|
50
|
+
rows.each do |row|
|
51
|
+
config.ui.stdout.puts("+#{"-" * width}+")
|
52
|
+
headers.each do |header|
|
53
|
+
entry_line = format_entry(header, max_key_length, row.send(:table)[header], max_value_length)
|
54
|
+
config.ui.stdout.puts(entry_line)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
config.ui.stdout.puts("+#{"-" * width}+")
|
58
|
+
OpenStruct.new(:rows => rows, :max_key_length => max_key_length, :max_value_length => max_value_length, :width => width)
|
59
|
+
else
|
60
|
+
OpenStruct.new(:rows => rows, :max_key_length => 0, :max_value_length => 0, :width => 0)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module ZTK
|
2
|
+
class Report
|
3
|
+
|
4
|
+
# Report Private Functionality
|
5
|
+
module Private
|
6
|
+
|
7
|
+
def max_spreadsheet_lengths(headers, rows)
|
8
|
+
max_lengths = OpenStruct.new
|
9
|
+
headers.each do |header|
|
10
|
+
collection = [header, rows.collect{|r| r.send(:table)[header] } ].flatten
|
11
|
+
maximum = collection.map(&:to_s).map(&:length).max
|
12
|
+
max_lengths.send(:table)[header] = maximum
|
13
|
+
end
|
14
|
+
|
15
|
+
max_lengths
|
16
|
+
end
|
17
|
+
|
18
|
+
def calculate_spreadsheet_width(headers, max_lengths)
|
19
|
+
header_lengths = ((headers.count * 3) - 3)
|
20
|
+
max_length = max_lengths.send(:table).values.reduce(:+)
|
21
|
+
(2 + max_length + header_lengths + 2)
|
22
|
+
end
|
23
|
+
|
24
|
+
def format_header(headers, lengths)
|
25
|
+
line = headers.collect do |header|
|
26
|
+
"-" * lengths.send(:table)[header]
|
27
|
+
end
|
28
|
+
|
29
|
+
["+-", line.join("-+-"), "-+"].join.strip
|
30
|
+
end
|
31
|
+
|
32
|
+
def format_row(*args)
|
33
|
+
spacer = " "
|
34
|
+
[spacer, args, spacer].flatten.join(" | ").strip
|
35
|
+
end
|
36
|
+
|
37
|
+
def format_entry(key, key_length, value, value_length)
|
38
|
+
"| %#{key_length}s: %-#{value_length}s |" % [key.to_s.upcase, value.to_s]
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module ZTK
|
2
|
+
class Report
|
3
|
+
|
4
|
+
# Report Spreadsheet Functionality
|
5
|
+
module Spreadsheet
|
6
|
+
|
7
|
+
# Displays data in a spreadsheet style.
|
8
|
+
#
|
9
|
+
# +-------------+-------+-------+--------+----------------+-------------------+--------------+---------+
|
10
|
+
# | NAME | ALIVE | ARCH | DISTRO | IP | MAC | CHEF VERSION | PERSIST |
|
11
|
+
# +-------------+-------+-------+--------+----------------+-------------------+--------------+---------+
|
12
|
+
# | sudo | false | amd64 | ubuntu | 192.168.99.110 | 00:00:5e:34:d6:aa | N/A | true |
|
13
|
+
# | timezone | false | amd64 | ubuntu | 192.168.122.47 | 00:00:5e:92:d7:f6 | N/A | true |
|
14
|
+
# | chef-client | false | amd64 | ubuntu | 192.168.159.98 | 00:00:5e:c7:ce:26 | N/A | true |
|
15
|
+
# | users | false | amd64 | ubuntu | 192.168.7.78 | 00:00:5e:89:f9:50 | N/A | true |
|
16
|
+
# +-------------+-------+-------+--------+----------------+-------------------+--------------+---------+
|
17
|
+
#
|
18
|
+
# @param [Array<Object>,Object] dataset A single object or an array of
|
19
|
+
# objects for which we want to generate a report
|
20
|
+
# @param [Array] headers An array of headers used for ordering the output.
|
21
|
+
# @return [OpenStruct]
|
22
|
+
def spreadsheet(dataset, headers, &block)
|
23
|
+
!block_given? and log_and_raise(ReportError, "You must supply a block!")
|
24
|
+
headers.nil? and log_and_raise(ReportError, "Headers can not be nil!")
|
25
|
+
dataset.nil? and log_and_raise(ReportError, "Dataset can not be nil!")
|
26
|
+
|
27
|
+
rows = Array.new
|
28
|
+
max_lengths = OpenStruct.new
|
29
|
+
headers = headers.map(&:to_s).map(&:downcase).map(&:to_sym)
|
30
|
+
|
31
|
+
if dataset.is_a?(Array)
|
32
|
+
dataset.each do |data|
|
33
|
+
rows << block.call(data)
|
34
|
+
end
|
35
|
+
else
|
36
|
+
rows << block.call(dataset)
|
37
|
+
end
|
38
|
+
rows.compact!
|
39
|
+
|
40
|
+
if rows.count > 0
|
41
|
+
max_lengths = max_spreadsheet_lengths(headers, rows)
|
42
|
+
header_line = headers.collect { |header| "%-#{max_lengths.send(:table)[header]}s" % header.to_s.upcase }
|
43
|
+
header_line = format_row(header_line)
|
44
|
+
|
45
|
+
config.ui.stdout.puts(format_header(headers, max_lengths))
|
46
|
+
config.ui.stdout.puts(header_line)
|
47
|
+
config.ui.stdout.puts(format_header(headers, max_lengths))
|
48
|
+
|
49
|
+
rows.each do |row|
|
50
|
+
row_line = headers.collect do |header|
|
51
|
+
header_length = max_lengths.send(:table)[header]
|
52
|
+
content = row.send(:table)[header]
|
53
|
+
|
54
|
+
"%-#{header_length}s" % content
|
55
|
+
end
|
56
|
+
|
57
|
+
row_line = format_row(row_line)
|
58
|
+
config.ui.stdout.puts(row_line)
|
59
|
+
end
|
60
|
+
|
61
|
+
config.ui.stdout.puts(format_header(headers, max_lengths))
|
62
|
+
OpenStruct.new(:rows => rows, :max_lengths => max_lengths, :width => calculate_spreadsheet_width(headers, max_lengths))
|
63
|
+
else
|
64
|
+
OpenStruct.new(:rows => rows, :max_lengths => 0, :width => 0)
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
data/lib/ztk/ssh.rb
CHANGED
@@ -1,8 +1,3 @@
|
|
1
|
-
require 'ostruct'
|
2
|
-
require 'net/ssh'
|
3
|
-
require 'net/ssh/proxy/command'
|
4
|
-
require 'net/sftp'
|
5
|
-
|
6
1
|
module ZTK
|
7
2
|
|
8
3
|
# SSH Error Class
|
@@ -24,7 +19,7 @@ module ZTK
|
|
24
19
|
#
|
25
20
|
# If you want to specify SSH options you can:
|
26
21
|
#
|
27
|
-
# keys = File.expand_path(File.join(
|
22
|
+
# keys = File.expand_path(File.join(Dir.home, '.ssh', 'id_rsa'))
|
28
23
|
# ssh = ZTK::SSH.new(:host_name => '127.0.0.1', :user => ENV['USER'], :keys => keys)
|
29
24
|
#
|
30
25
|
# = Configuration Examples:
|
@@ -41,8 +36,8 @@ module ZTK
|
|
41
36
|
# Specify an identity file:
|
42
37
|
#
|
43
38
|
# ssh.config do |config|
|
44
|
-
# config.keys = File.expand_path(File.join(
|
45
|
-
# config.proxy_keys = File.expand_path(File.join(
|
39
|
+
# config.keys = File.expand_path(File.join(Dir.home, '.ssh', 'id_rsa'))
|
40
|
+
# config.proxy_keys = File.expand_path(File.join(Dir.home, '.ssh', 'id_rsa'))
|
46
41
|
# end
|
47
42
|
#
|
48
43
|
# Specify a timeout:
|
@@ -65,6 +60,11 @@ module ZTK
|
|
65
60
|
#
|
66
61
|
# @author Zachary Patten <zachary AT jovelabs DOT com>
|
67
62
|
class SSH < ZTK::Base
|
63
|
+
require 'ostruct'
|
64
|
+
require 'net/ssh'
|
65
|
+
require 'net/ssh/proxy/command'
|
66
|
+
require 'net/sftp'
|
67
|
+
|
68
68
|
# Exit Signal Mappings
|
69
69
|
EXIT_SIGNALS = {
|
70
70
|
1 => "SIGHUP",
|
@@ -96,15 +96,20 @@ module ZTK
|
|
96
96
|
27 => "SIGPROF"
|
97
97
|
}
|
98
98
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
99
|
+
require 'ztk/ssh/bootstrap'
|
100
|
+
require 'ztk/ssh/command'
|
101
|
+
require 'ztk/ssh/console'
|
102
|
+
require 'ztk/ssh/core'
|
103
|
+
require 'ztk/ssh/download'
|
104
|
+
require 'ztk/ssh/exec'
|
105
|
+
require 'ztk/ssh/file'
|
106
|
+
require 'ztk/ssh/private'
|
107
|
+
require 'ztk/ssh/upload'
|
105
108
|
|
106
109
|
include ZTK::SSH::Bootstrap
|
107
110
|
include ZTK::SSH::Command
|
111
|
+
include ZTK::SSH::Console
|
112
|
+
include ZTK::SSH::Core
|
108
113
|
include ZTK::SSH::Download
|
109
114
|
include ZTK::SSH::Exec
|
110
115
|
include ZTK::SSH::File
|
@@ -155,113 +160,14 @@ module ZTK
|
|
155
160
|
:exit_code => 0,
|
156
161
|
:silence => false
|
157
162
|
}.merge(configuration))
|
158
|
-
config.ui.logger.debug { "config=#{config.send(:table).inspect}" }
|
159
|
-
end
|
160
|
-
|
161
|
-
# Starts an SSH session. Can also be used to get the Net::SSH object.
|
162
|
-
#
|
163
|
-
# Primarily used internally.
|
164
|
-
def ssh
|
165
|
-
@ssh ||= Net::SSH.start(config.host_name, config.user, ssh_options)
|
166
|
-
end
|
167
|
-
|
168
|
-
# Starts an SFTP session. Can also be used to get the Net::SFTP object.
|
169
|
-
#
|
170
|
-
# Primarily used internally.
|
171
|
-
def sftp
|
172
|
-
@sftp ||= Net::SFTP.start(config.host_name, config.user, ssh_options)
|
173
|
-
end
|
174
|
-
|
175
|
-
# Close our session gracefully.
|
176
|
-
def close
|
177
|
-
config.ui.logger.debug { "close" }
|
178
|
-
ssh and !ssh.closed? and ssh.close
|
179
|
-
end
|
180
|
-
|
181
|
-
# The on_retry method we'll use with the RescueRetry class.
|
182
|
-
def on_retry(exception)
|
183
|
-
close
|
184
|
-
@ssh = nil
|
185
|
-
@sftp = nil
|
186
|
-
end
|
187
163
|
|
188
|
-
# Launches an SSH console, replacing the current process with the console
|
189
|
-
# process.
|
190
|
-
#
|
191
|
-
# @example Launch a console:
|
192
|
-
# ssh = ZTK::SSH.new
|
193
|
-
# ssh.config do |config|
|
194
|
-
# config.user = ENV["USER"]
|
195
|
-
# config.host_name = "127.0.0.1"
|
196
|
-
# end
|
197
|
-
# ssh.console
|
198
|
-
def console
|
199
164
|
config.ui.logger.debug { "config=#{config.send(:table).inspect}" }
|
200
|
-
config.ui.logger.info { "console(#{console_command.inspect})" }
|
201
|
-
|
202
|
-
config.ui.logger.fatal { "REPLACING CURRENT PROCESS - GOODBYE!" }
|
203
|
-
Kernel.exec(console_command)
|
204
165
|
end
|
205
166
|
|
206
167
|
|
207
168
|
private
|
208
169
|
|
209
|
-
|
210
|
-
def ssh_options
|
211
|
-
options = {}
|
212
|
-
|
213
|
-
# These are plainly documented on the Net::SSH config class.
|
214
|
-
options.merge!(:encryption => config.encryption) if config.encryption
|
215
|
-
options.merge!(:compression => config.compression) if config.compression
|
216
|
-
options.merge!(:compression_level => config.compression_level) if config.compression_level
|
217
|
-
options.merge!(:timeout => config.timeout) if config.timeout
|
218
|
-
options.merge!(:forward_agent => config.forward_agent) if config.forward_agent
|
219
|
-
options.merge!(:global_known_hosts_file => config.global_known_hosts_file) if config.global_known_hosts_file
|
220
|
-
options.merge!(:auth_methods => config.auth_methods) if config.auth_methods
|
221
|
-
options.merge!(:host_key => config.host_key) if config.host_key
|
222
|
-
options.merge!(:host_key_alias => config.host_key_alias) if config.host_key_alias
|
223
|
-
options.merge!(:host_name => config.host_name) if config.host_name
|
224
|
-
options.merge!(:keys => config.keys) if config.keys
|
225
|
-
options.merge!(:keys_only => config.keys_only) if config.keys_only
|
226
|
-
options.merge!(:hmac => config.hmac) if config.hmac
|
227
|
-
options.merge!(:port => config.port) if config.port
|
228
|
-
options.merge!(:proxy => Net::SSH::Proxy::Command.new(proxy_command)) if config.proxy_host_name
|
229
|
-
options.merge!(:rekey_limit => config.rekey_limit) if config.rekey_limit
|
230
|
-
options.merge!(:user => config.user) if config.user
|
231
|
-
options.merge!(:user_known_hosts_file => config.user_known_hosts_file) if config.user_known_hosts_file
|
232
|
-
|
233
|
-
# This is not plainly documented on the Net::SSH config class.
|
234
|
-
options.merge!(:password => config.password) if config.password
|
235
|
-
|
236
|
-
config.ui.logger.debug { "ssh_options(#{options.inspect})" }
|
237
|
-
options
|
238
|
-
end
|
239
|
-
|
240
|
-
# Builds a human readable tag about our connection. Used for internal
|
241
|
-
# logging purposes.
|
242
|
-
def tag
|
243
|
-
tags = Array.new
|
244
|
-
|
245
|
-
if config.proxy_host_name
|
246
|
-
proxy_user_host = "#{config.proxy_user}@#{config.proxy_host_name}"
|
247
|
-
proxy_port = (config.proxy_port ? ":#{config.proxy_port}" : nil)
|
248
|
-
tags << [proxy_user_host, proxy_port].compact.join
|
249
|
-
tags << " >>> "
|
250
|
-
end
|
251
|
-
|
252
|
-
user_host = "#{config.user}@#{config.host_name}"
|
253
|
-
port = (config.port ? ":#{config.port}" : nil)
|
254
|
-
tags << [user_host, port].compact.join
|
255
|
-
|
256
|
-
tags.join.strip
|
257
|
-
end
|
258
|
-
|
259
|
-
def log_header(what)
|
260
|
-
count = 8
|
261
|
-
sep = ("=" * count)
|
262
|
-
header = [sep, "[ #{what} ]", sep, "[ #{tag} ]", sep, "[ #{what} ]", sep].join
|
263
|
-
"#{header}\n"
|
264
|
-
end
|
170
|
+
include ZTK::SSH::Private
|
265
171
|
|
266
172
|
end
|
267
173
|
|
data/lib/ztk/ssh/bootstrap.rb
CHANGED
@@ -32,7 +32,7 @@ module ZTK
|
|
32
32
|
tempfile = Tempfile.new("bootstrap")
|
33
33
|
|
34
34
|
local_tempfile = tempfile.path
|
35
|
-
remote_tempfile = ::File.join(
|
35
|
+
remote_tempfile = ::File.join(ZTK::Locator.root, "tmp", ::File.basename(local_tempfile))
|
36
36
|
|
37
37
|
::File.open(local_tempfile, 'w') do |file|
|
38
38
|
file.puts(content)
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module ZTK
|
2
|
+
class SSH
|
3
|
+
|
4
|
+
# SSH Console Functionality
|
5
|
+
module Console
|
6
|
+
|
7
|
+
# Launches an SSH console, replacing the current process with the console
|
8
|
+
# process.
|
9
|
+
#
|
10
|
+
# @example Launch a console:
|
11
|
+
# ssh = ZTK::SSH.new
|
12
|
+
# ssh.config do |config|
|
13
|
+
# config.user = ENV["USER"]
|
14
|
+
# config.host_name = "127.0.0.1"
|
15
|
+
# end
|
16
|
+
# ssh.console
|
17
|
+
def console
|
18
|
+
config.ui.logger.debug { "config=#{config.send(:table).inspect}" }
|
19
|
+
config.ui.logger.info { "console(#{console_command.inspect})" }
|
20
|
+
|
21
|
+
config.ui.logger.fatal { "REPLACING CURRENT PROCESS - GOODBYE!" }
|
22
|
+
Kernel.exec(console_command)
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
data/lib/ztk/ssh/core.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
module ZTK
|
2
|
+
class SSH
|
3
|
+
|
4
|
+
# SSH Core Functionality
|
5
|
+
module Core
|
6
|
+
|
7
|
+
# Starts an SSH session. Can also be used to get the Net::SSH object.
|
8
|
+
#
|
9
|
+
# Primarily used internally.
|
10
|
+
def ssh
|
11
|
+
@ssh ||= Net::SSH.start(config.host_name, config.user, ssh_options)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Starts an SFTP session. Can also be used to get the Net::SFTP object.
|
15
|
+
#
|
16
|
+
# Primarily used internally.
|
17
|
+
def sftp
|
18
|
+
@sftp ||= Net::SFTP.start(config.host_name, config.user, ssh_options)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Close our session gracefully.
|
22
|
+
def close
|
23
|
+
config.ui.logger.debug { "close" }
|
24
|
+
ssh and !ssh.closed? and ssh.close
|
25
|
+
end
|
26
|
+
|
27
|
+
# The on_retry method we'll use with the RescueRetry class.
|
28
|
+
def on_retry(exception)
|
29
|
+
close
|
30
|
+
@ssh = nil
|
31
|
+
@sftp = nil
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
data/lib/ztk/ssh/download.rb
CHANGED
@@ -21,8 +21,8 @@ module ZTK
|
|
21
21
|
# config.user = ENV["USER"]
|
22
22
|
# config.host_name = "127.0.0.1"
|
23
23
|
# end
|
24
|
-
# local = File.expand_path(File.join("
|
25
|
-
# remote = File.expand_path(File.join(
|
24
|
+
# local = File.expand_path(File.join(ZTK::Locator.root, "tmp", "id_rsa.pub"))
|
25
|
+
# remote = File.expand_path(File.join(Dir.home, ".ssh", "id_rsa.pub"))
|
26
26
|
# ssh.download(remote, local)
|
27
27
|
def download(remote, local, options={})
|
28
28
|
options = {:recursive => ::File.directory?(local) }.merge(options)
|