ztk 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/.gitignore +1 -0
- data/lib/ztk.rb +2 -0
- data/lib/ztk/base.rb +36 -16
- data/lib/ztk/command.rb +103 -0
- data/lib/ztk/parallel.rb +35 -29
- data/lib/ztk/spinner.rb +71 -0
- data/lib/ztk/ssh.rb +223 -91
- data/lib/ztk/tcp_socket_check.rb +12 -12
- data/lib/ztk/template.rb +2 -2
- data/lib/ztk/version.rb +1 -1
- data/spec/spec_helper.rb +0 -8
- data/spec/ztk/command_spec.rb +78 -0
- data/spec/ztk/parallel_spec.rb +29 -14
- data/spec/ztk/ssh_spec.rb +29 -12
- data/spec/ztk/tcp_socket_check_spec.rb +90 -25
- data/spec/ztk/template_spec.rb +7 -0
- data/ztk.gemspec +2 -0
- metadata +41 -5
- data/WIKI.md +0 -439
data/lib/ztk.rb
CHANGED
@@ -24,8 +24,10 @@ module ZTK
|
|
24
24
|
class Error < StandardError; end
|
25
25
|
|
26
26
|
autoload :Base, "ztk/base"
|
27
|
+
autoload :Command, "ztk/command"
|
27
28
|
autoload :Logger, "ztk/logger"
|
28
29
|
autoload :Parallel, "ztk/parallel"
|
30
|
+
autoload :Spinner, "ztk/spinner"
|
29
31
|
autoload :SSH, "ztk/ssh"
|
30
32
|
autoload :TCPSocketCheck, "ztk/tcp_socket_check"
|
31
33
|
autoload :Template, "ztk/template"
|
data/lib/ztk/base.rb
CHANGED
@@ -20,20 +20,24 @@
|
|
20
20
|
|
21
21
|
require "ostruct"
|
22
22
|
|
23
|
-
################################################################################
|
24
|
-
|
25
23
|
module ZTK
|
26
24
|
|
27
|
-
|
28
|
-
|
25
|
+
# ZTK::Base error class
|
29
26
|
class BaseError < Error; end
|
30
27
|
|
31
|
-
|
32
|
-
|
28
|
+
# This is the base class inherited by most of the other classes in this
|
29
|
+
# library. It provides a standard set of features to control STDOUT, STDERR
|
30
|
+
# and STDIN, a configuration mechanism and logging mechanism.
|
31
|
+
#
|
32
|
+
# You should never interact with this class directly; you should inherit it
|
33
|
+
# and extend functionality as appropriate.
|
33
34
|
class Base
|
34
35
|
|
35
|
-
|
36
|
-
|
36
|
+
# @param [Hash] config configuration options hash
|
37
|
+
# @option config [IO] :stdout instance of IO to be used for STDOUT
|
38
|
+
# @option config [IO] :stderr instance of IO to be used for STDERR
|
39
|
+
# @option config [IO] :stdin instance of IO to be used for STDIN
|
40
|
+
# @option config [Logger] :logger instance of Logger to be used for logging
|
37
41
|
def initialize(config={})
|
38
42
|
defined?(Rails) and rails_logger = Rails.logger
|
39
43
|
@config = OpenStruct.new({
|
@@ -48,11 +52,17 @@ module ZTK
|
|
48
52
|
@config.stdin.respond_to?(:sync=) and @config.stdin.sync = true
|
49
53
|
@config.logger.respond_to?(:sync=) and @config.logger.sync = true
|
50
54
|
|
51
|
-
|
55
|
+
log(:debug) { "config(#{@config.inspect})" }
|
52
56
|
end
|
53
57
|
|
54
|
-
|
55
|
-
|
58
|
+
# Configuration OpenStruct accessor method.
|
59
|
+
#
|
60
|
+
# If no block is given, the method will return the configuration OpenStruct
|
61
|
+
# object. If a block is given, the block is yielded with the configuration
|
62
|
+
# OpenStruct object.
|
63
|
+
#
|
64
|
+
# @yieldparam [OpenStruct] config The configuration OpenStruct object.
|
65
|
+
# @return [OpenStruct] The configuration OpenStruct object.
|
56
66
|
def config(&block)
|
57
67
|
if block_given?
|
58
68
|
block.call(@config)
|
@@ -61,12 +71,22 @@ module ZTK
|
|
61
71
|
end
|
62
72
|
end
|
63
73
|
|
64
|
-
|
74
|
+
# Base logging method.
|
75
|
+
#
|
76
|
+
# The value returned in the block is passed down to the logger specified in
|
77
|
+
# the classes configuration.
|
78
|
+
#
|
79
|
+
# @param [Symbol] method_name This should be any one of [:debug, :info, :warn, :error, :fatal].
|
80
|
+
# @yield No value is passed to the block.
|
81
|
+
# @yieldreturn [String] The message to log.
|
82
|
+
def log(method_name, &block)
|
83
|
+
if block_given?
|
84
|
+
@config.logger and @config.logger.method(method_name.to_sym).call { yield }
|
85
|
+
else
|
86
|
+
raise(Error, "You must supply a block to the log method!")
|
87
|
+
end
|
88
|
+
end
|
65
89
|
|
66
90
|
end
|
67
91
|
|
68
|
-
################################################################################
|
69
|
-
|
70
92
|
end
|
71
|
-
|
72
|
-
################################################################################
|
data/lib/ztk/command.rb
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
################################################################################
|
2
|
+
#
|
3
|
+
# Author: Zachary Patten <zachary@jovelabs.com>
|
4
|
+
# Copyright: Copyright (c) Jove Labs
|
5
|
+
# License: Apache License, Version 2.0
|
6
|
+
#
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
# you may not use this file except in compliance with the License.
|
9
|
+
# You may obtain a copy of the License at
|
10
|
+
#
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
#
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
+
# See the License for the specific language governing permissions and
|
17
|
+
# limitations under the License.
|
18
|
+
#
|
19
|
+
################################################################################
|
20
|
+
|
21
|
+
require "ostruct"
|
22
|
+
|
23
|
+
################################################################################
|
24
|
+
|
25
|
+
module ZTK
|
26
|
+
|
27
|
+
################################################################################
|
28
|
+
|
29
|
+
class CommandError < Error; end
|
30
|
+
|
31
|
+
################################################################################
|
32
|
+
|
33
|
+
class Command < ZTK::Base
|
34
|
+
|
35
|
+
################################################################################
|
36
|
+
|
37
|
+
def initialize(config={})
|
38
|
+
super(config)
|
39
|
+
end
|
40
|
+
|
41
|
+
################################################################################
|
42
|
+
|
43
|
+
def exec(command, options={})
|
44
|
+
options = OpenStruct.new({ :exit_code => 0, :silence => false }.merge(options))
|
45
|
+
log(:debug) { "config(#{@config.inspect})" }
|
46
|
+
log(:debug) { "options(#{options.inspect})" }
|
47
|
+
log(:debug) { "command(#{command.inspect})" }
|
48
|
+
|
49
|
+
parent_stdout_reader, child_stdout_writer = IO.pipe
|
50
|
+
parent_stderr_reader, child_stderr_writer = IO.pipe
|
51
|
+
|
52
|
+
pid = Process.fork do
|
53
|
+
parent_stdout_reader.close
|
54
|
+
parent_stderr_reader.close
|
55
|
+
|
56
|
+
STDOUT.reopen(child_stdout_writer)
|
57
|
+
STDERR.reopen(child_stderr_writer)
|
58
|
+
STDIN.reopen("/dev/null")
|
59
|
+
|
60
|
+
child_stdout_writer.close
|
61
|
+
child_stderr_writer.close
|
62
|
+
|
63
|
+
Kernel.exec(command)
|
64
|
+
end
|
65
|
+
child_stdout_writer.close
|
66
|
+
child_stderr_writer.close
|
67
|
+
|
68
|
+
Process.waitpid(pid)
|
69
|
+
|
70
|
+
@config.stdout.write(parent_stdout_reader.read) unless options.silence
|
71
|
+
@config.stderr.write(parent_stderr_reader.read) unless options.silence
|
72
|
+
|
73
|
+
parent_stdout_reader.close
|
74
|
+
parent_stderr_reader.close
|
75
|
+
|
76
|
+
log(:debug) { "exit_code(#{$?.inspect})" }
|
77
|
+
|
78
|
+
if ($? != options.exit_code)
|
79
|
+
message = "exec(#{command.inspect}, #{options.inspect}) failed! [#{$?.inspect}]"
|
80
|
+
log(:fatal) { message }
|
81
|
+
raise CommandError, message
|
82
|
+
end
|
83
|
+
|
84
|
+
$?
|
85
|
+
end
|
86
|
+
|
87
|
+
def upload(*args)
|
88
|
+
raise CommandError, "Not Implemented"
|
89
|
+
end
|
90
|
+
|
91
|
+
def download(*args)
|
92
|
+
raise CommandError, "Not Implemented"
|
93
|
+
end
|
94
|
+
|
95
|
+
################################################################################
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
################################################################################
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
################################################################################
|
data/lib/ztk/parallel.rb
CHANGED
@@ -2,9 +2,9 @@
|
|
2
2
|
#
|
3
3
|
# Author: Zachary Patten <zachary@jovelabs.com>
|
4
4
|
# Copyright: Copyright (c) Jove Labs
|
5
|
-
# License: Apache License,
|
5
|
+
# License: Apache License, VersIOn 2.0
|
6
6
|
#
|
7
|
-
# Licensed under the Apache License,
|
7
|
+
# Licensed under the Apache License, VersIOn 2.0 (the "License");
|
8
8
|
# you may not use this file except in compliance with the License.
|
9
9
|
# You may obtain a copy of the License at
|
10
10
|
#
|
@@ -12,9 +12,9 @@
|
|
12
12
|
#
|
13
13
|
# Unless required by applicable law or agreed to in writing, software
|
14
14
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
-
# WITHOUT WARRANTIES OR
|
16
|
-
# See the License for the specific language governing
|
17
|
-
#
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
+
# See the License for the specific language governing permissIOns and
|
17
|
+
# limitatIOns under the License.
|
18
18
|
#
|
19
19
|
################################################################################
|
20
20
|
|
@@ -26,7 +26,11 @@ module ZTK
|
|
26
26
|
|
27
27
|
################################################################################
|
28
28
|
|
29
|
-
class
|
29
|
+
class ParallelError < Error; end
|
30
|
+
|
31
|
+
################################################################################
|
32
|
+
|
33
|
+
class Parallel < ZTK::Base
|
30
34
|
|
31
35
|
################################################################################
|
32
36
|
|
@@ -41,40 +45,42 @@ module ZTK
|
|
41
45
|
:after_fork => nil
|
42
46
|
}.merge(config))
|
43
47
|
|
44
|
-
@forks =
|
45
|
-
@results =
|
46
|
-
|
48
|
+
@forks = Array.new
|
49
|
+
@results = Array.new
|
50
|
+
GC.respond_to?(:copy_on_write_friendly=) and GC.copy_on_write_friendly = true
|
47
51
|
end
|
48
52
|
|
49
53
|
################################################################################
|
50
54
|
|
51
|
-
def process(
|
52
|
-
|
55
|
+
def process(&block)
|
56
|
+
raise ParallelError, "You must supply a block to the process method!" if !block_given?
|
57
|
+
|
58
|
+
log(:debug) { "forks(#{@forks.inspect})" }
|
53
59
|
|
54
60
|
while (@forks.count >= @config.max_forks) do
|
55
61
|
wait
|
56
62
|
end
|
57
63
|
|
58
|
-
child_reader, parent_writer =
|
59
|
-
parent_reader, child_writer =
|
64
|
+
child_reader, parent_writer = IO.pipe
|
65
|
+
parent_reader, child_writer = IO.pipe
|
60
66
|
|
61
|
-
@config.before_fork and @config.before_fork.call(
|
62
|
-
pid =
|
63
|
-
@config.after_fork and @config.after_fork.call(
|
67
|
+
@config.before_fork and @config.before_fork.call(Process.pid)
|
68
|
+
pid = Process.fork do
|
69
|
+
@config.after_fork and @config.after_fork.call(Process.pid)
|
64
70
|
|
65
71
|
parent_writer.close
|
66
72
|
parent_reader.close
|
67
73
|
|
68
|
-
if !(data =
|
69
|
-
|
70
|
-
child_writer.write(
|
74
|
+
if !(data = block.call).nil?
|
75
|
+
log(:debug) { "write(#{data.inspect})" }
|
76
|
+
child_writer.write(Base64.encode64(Marshal.dump(data)))
|
71
77
|
end
|
72
78
|
|
73
79
|
child_reader.close
|
74
80
|
child_writer.close
|
75
|
-
|
81
|
+
Process.exit!(0)
|
76
82
|
end
|
77
|
-
@config.after_fork and @config.after_fork.call(
|
83
|
+
@config.after_fork and @config.after_fork.call(Process.pid)
|
78
84
|
|
79
85
|
child_reader.close
|
80
86
|
child_writer.close
|
@@ -88,11 +94,11 @@ module ZTK
|
|
88
94
|
################################################################################
|
89
95
|
|
90
96
|
def wait
|
91
|
-
|
92
|
-
pid, status = (
|
97
|
+
log(:debug) { "forks(#{@forks.inspect})" }
|
98
|
+
pid, status = (Process.wait2(-1) rescue nil)
|
93
99
|
if !pid.nil? && !status.nil? && !(fork = @forks.select{ |f| f[:pid] == pid }.first).nil?
|
94
|
-
data = (
|
95
|
-
|
100
|
+
data = (Marshal.load(Base64.decode64(fork[:reader].read.to_s)) rescue nil)
|
101
|
+
log(:debug) { "read(#{data.inspect})" }
|
96
102
|
!data.nil? and @results.push(data)
|
97
103
|
fork[:reader].close
|
98
104
|
fork[:writer].close
|
@@ -100,16 +106,16 @@ module ZTK
|
|
100
106
|
@forks -= [fork]
|
101
107
|
return [pid, status, data]
|
102
108
|
end
|
103
|
-
sleep(1)
|
104
109
|
nil
|
105
110
|
end
|
106
111
|
|
107
112
|
################################################################################
|
108
113
|
|
109
114
|
def waitall
|
110
|
-
data =
|
115
|
+
data = Array.new
|
111
116
|
while @forks.count > 0
|
112
|
-
|
117
|
+
result = self.wait
|
118
|
+
result and data << result
|
113
119
|
end
|
114
120
|
data
|
115
121
|
end
|
@@ -117,7 +123,7 @@ module ZTK
|
|
117
123
|
################################################################################
|
118
124
|
|
119
125
|
def count
|
120
|
-
|
126
|
+
log(:debug) { "count(#{@forks.count})" }
|
121
127
|
@forks.count
|
122
128
|
end
|
123
129
|
|
data/lib/ztk/spinner.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
################################################################################
|
2
|
+
#
|
3
|
+
# Author: Zachary Patten <zachary@jovelabs.com>
|
4
|
+
# Copyright: Copyright (c) Jove Labs
|
5
|
+
# License: Apache License, Version 2.0
|
6
|
+
#
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
# you may not use this file except in compliance with the License.
|
9
|
+
# You may obtain a copy of the License at
|
10
|
+
#
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
#
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
+
# See the License for the specific language governing permissions and
|
17
|
+
# limitations under the License.
|
18
|
+
#
|
19
|
+
################################################################################
|
20
|
+
|
21
|
+
require "ostruct"
|
22
|
+
|
23
|
+
################################################################################
|
24
|
+
|
25
|
+
module ZTK
|
26
|
+
|
27
|
+
################################################################################
|
28
|
+
|
29
|
+
class SpinnerError < Error; end
|
30
|
+
|
31
|
+
################################################################################
|
32
|
+
|
33
|
+
class Spinner
|
34
|
+
|
35
|
+
################################################################################
|
36
|
+
|
37
|
+
class << self
|
38
|
+
|
39
|
+
################################################################################
|
40
|
+
|
41
|
+
def spin(stdout=$stdout)
|
42
|
+
charset = %w( | / - \\ )
|
43
|
+
count = 0
|
44
|
+
spinner = Thread.new do
|
45
|
+
while count do
|
46
|
+
stdout.print(charset[(count += 1) % charset.length])
|
47
|
+
stdout.respond_to?(:flush) and stdout.flush
|
48
|
+
sleep(0.25)
|
49
|
+
stdout.print("\b")
|
50
|
+
stdout.respond_to?(:flush) and stdout.flush
|
51
|
+
end
|
52
|
+
end
|
53
|
+
yield.tap do
|
54
|
+
count = false
|
55
|
+
spinner.join
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
################################################################################
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
################################################################################
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
################################################################################
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
################################################################################
|
data/lib/ztk/ssh.rb
CHANGED
@@ -23,126 +23,213 @@ require "net/ssh"
|
|
23
23
|
require "net/ssh/proxy/command"
|
24
24
|
require "net/sftp"
|
25
25
|
|
26
|
-
################################################################################
|
27
|
-
|
28
26
|
module ZTK
|
29
27
|
|
30
|
-
|
31
|
-
|
28
|
+
# ZTK::SSH error class
|
32
29
|
class SSHError < Error; end
|
33
30
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
31
|
+
# We can get a new instance of SSH like so:
|
32
|
+
# ssh = ZTK::SSH.new
|
33
|
+
#
|
34
|
+
# If we wanted to redirect STDOUT and STDERR to a StringIO we can do this:
|
35
|
+
# std_combo = StringIO.new
|
36
|
+
# ssh = ZTK::SSH.new(:stdout => std_combo, :stderr => std_combo)
|
37
|
+
#
|
38
|
+
# If you want to specify SSH options you can:
|
39
|
+
# keys = File.expand_path(File.join(ENV['HOME'], '.ssh', 'id_rsa'))
|
40
|
+
# ssh = ZTK::SSH.new(:host_name => '127.0.0.1', :user => ENV['USER'], :keys => keys)
|
41
|
+
#
|
42
|
+
# = Configuration Examples:
|
43
|
+
#
|
44
|
+
# To proxy through another host, for example SSH to 192.168.1.1 through 192.168.0.1:
|
45
|
+
# ssh.config do |config|
|
46
|
+
# config.user = ENV['USER']
|
47
|
+
# config.host_name = '192.168.1.1'
|
48
|
+
# config.proxy_user = ENV['USER']
|
49
|
+
# config.proxy_host_name = '192.168.0.1'
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
# Specify an identity file:
|
53
|
+
# ssh.config do |config|
|
54
|
+
# config.keys = File.expand_path(File.join(ENV['HOME'], '.ssh', 'id_rsa'))
|
55
|
+
# config.proxy_keys = File.expand_path(File.join(ENV['HOME'], '.ssh', 'id_rsa'))
|
56
|
+
# end
|
57
|
+
#
|
58
|
+
# Specify a timeout:
|
59
|
+
# ssh.config do |config|
|
60
|
+
# config.timeout = 30
|
61
|
+
# end
|
62
|
+
#
|
63
|
+
# Specify a password:
|
64
|
+
# ssh.config do |config|
|
65
|
+
# config.password = 'p@$$w0rd'
|
66
|
+
# end
|
67
|
+
#
|
68
|
+
# Check host keys, the default is false (off):
|
69
|
+
# ssh.config do |config|
|
70
|
+
# config.host_key_verify = true
|
71
|
+
# end
|
72
|
+
class SSH < ZTK::Base
|
73
|
+
|
74
|
+
# @param [Hash] config Configuration options hash.
|
75
|
+
# @option config [String] :host_name Server hostname to connect to.
|
76
|
+
# @option config [String] :user Username to use for authentication.
|
77
|
+
# @option config [String, Array<String>] :keys A single or series of identity files to use for authentication.
|
78
|
+
# @option config [String] :password Password to use for authentication.
|
79
|
+
# @option config [Integer] :timeout SSH connection timeout to use.
|
80
|
+
# @option config [Boolean] :compression Weither or not to use compression for this session.
|
81
|
+
# @option config [Integer] :compression_level What level of compression to use.
|
82
|
+
# @option config [String] :proxy_host_name Server hostname to proxy through.
|
83
|
+
# @option config [String] :proxy_user Username to use for proxy authentication.
|
84
|
+
# @option config [String, Array<String>] :proxy_keys A single or series of identity files to use for authentication with the proxy.
|
40
85
|
def initialize(config={})
|
41
|
-
super(
|
42
|
-
:ssh => ::OpenStruct.new
|
43
|
-
}.merge(config))
|
86
|
+
super(config)
|
44
87
|
end
|
45
88
|
|
46
|
-
|
47
|
-
|
89
|
+
# Launches an SSH console, replacing the current process with the console process.
|
90
|
+
#
|
91
|
+
# @example Launch a console:
|
92
|
+
# $logger = ZTK::Logger.new(STDOUT)
|
93
|
+
# ssh = ZTK::SSH.new
|
94
|
+
# ssh.config do |config|
|
95
|
+
# config.user = ENV["USER"]
|
96
|
+
# config.host_name = "127.0.0.1"
|
97
|
+
# end
|
98
|
+
# ssh.console
|
48
99
|
def console
|
49
|
-
|
100
|
+
log(:debug) { "console" }
|
101
|
+
log(:debug) { "config(#{@config.inspect})" }
|
50
102
|
|
51
|
-
|
52
|
-
command << [ "-q" ]
|
53
|
-
command << [ "-o", "UserKnownHostsFile=/dev/null" ]
|
54
|
-
command << [ "-o", "StrictHostKeyChecking=no" ]
|
55
|
-
command << [ "-o", "KeepAlive=yes" ]
|
56
|
-
command << [ "-o", "ServerAliveInterval=60" ]
|
57
|
-
command << [ "-i", @config.ssh.identity_file ] if @config.ssh.identity_file
|
58
|
-
command << [ "-o", "ProxyCommand=\"#{proxy_command}\"" ] if @config.ssh.proxy
|
59
|
-
command << "#{@config.ssh.user}@#{@config.ssh.host}"
|
60
|
-
command = command.flatten.compact.join(" ")
|
61
|
-
@config.logger and @config.logger.info { "command(#{command})" }
|
62
|
-
::Kernel.exec(command)
|
103
|
+
Kernel.exec(console_command)
|
63
104
|
end
|
64
105
|
|
65
|
-
|
66
|
-
|
106
|
+
# Executes a command on the remote host.
|
107
|
+
#
|
108
|
+
# @param [String] command The command to execute.
|
109
|
+
# @param [Hash] options The options hash for executing the command.
|
110
|
+
# @option options [Boolean] :silence Squelch output to STDOUT and STDERR. If the log level is :debug, STDOUT and STDERR will go to the log file regardless of this setting. STDOUT and STDERR are always returned in the output return value regardless of this setting.
|
111
|
+
#
|
112
|
+
# @return [OpenStruct#output] The output of the command, both STDOUT and STDERR.
|
113
|
+
# @return [OpenStruct#exit] The exit status (i.e. $?).
|
114
|
+
#
|
115
|
+
# @example Execute a command:
|
116
|
+
# $logger = ZTK::Logger.new(STDOUT)
|
117
|
+
# ssh = ZTK::SSH.new
|
118
|
+
# ssh.config do |config|
|
119
|
+
# config.user = ENV["USER"]
|
120
|
+
# config.host_name = "127.0.0.1"
|
121
|
+
# end
|
122
|
+
# puts ssh.exec("hostname -f").output
|
67
123
|
def exec(command, options={})
|
68
|
-
|
124
|
+
log(:debug) { "exec(#{command.inspect}, #{options.inspect})" }
|
125
|
+
log(:debug) { "config(#{@config.inspect})" }
|
69
126
|
|
70
|
-
|
71
|
-
|
72
|
-
output = ""
|
127
|
+
@ssh ||= Net::SSH.start(@config.host_name, @config.user, ssh_options)
|
128
|
+
log(:debug) { "ssh(#{@ssh.inspect})" }
|
73
129
|
|
74
|
-
|
75
|
-
|
76
|
-
|
130
|
+
options = OpenStruct.new({ :silence => false }.merge(options))
|
131
|
+
log(:debug) { "options(#{options.inspect})" }
|
132
|
+
|
133
|
+
output = ""
|
77
134
|
channel = @ssh.open_channel do |chan|
|
78
|
-
|
135
|
+
log(:debug) { "channel opened" }
|
79
136
|
chan.exec(command) do |ch, success|
|
80
137
|
raise SSHError, "Could not execute '#{command}'." unless success
|
81
138
|
|
82
139
|
ch.on_data do |c, data|
|
83
|
-
|
84
|
-
@config.
|
85
|
-
|
140
|
+
log(:debug) { data.chomp.strip }
|
141
|
+
@config.stdout.print(data) unless options.silence
|
142
|
+
output += data.chomp.strip
|
86
143
|
end
|
87
144
|
|
88
145
|
ch.on_extended_data do |c, type, data|
|
89
|
-
|
90
|
-
@config.
|
91
|
-
|
146
|
+
log(:debug) { data.chomp.strip }
|
147
|
+
@config.stderr.print(data) unless options.silence
|
148
|
+
output += data.chomp.strip
|
92
149
|
end
|
93
150
|
|
94
151
|
end
|
95
152
|
end
|
96
153
|
channel.wait
|
97
|
-
|
154
|
+
log(:debug) { "channel closed" }
|
98
155
|
|
99
|
-
output
|
156
|
+
OpenStruct.new(:output => output, :exit => $?)
|
100
157
|
end
|
101
158
|
|
102
|
-
|
103
|
-
|
159
|
+
# Uploads a local file to a remote host.
|
160
|
+
#
|
161
|
+
# @param [String] local The local file/path you wish to upload from.
|
162
|
+
# @param [String] remote The remote file/path you with to upload to.
|
163
|
+
#
|
164
|
+
# @example Upload a file:
|
165
|
+
# $logger = ZTK::Logger.new(STDOUT)
|
166
|
+
# ssh = ZTK::SSH.new
|
167
|
+
# ssh.config do |config|
|
168
|
+
# config.user = ENV["USER"]
|
169
|
+
# config.host_name = "127.0.0.1"
|
170
|
+
# end
|
171
|
+
# local = File.expand_path(File.join(ENV["HOME"], ".ssh", "id_rsa.pub"))
|
172
|
+
# remote = File.expand_path(File.join("/tmp", "id_rsa.pub"))
|
173
|
+
# ssh.upload(local, remote)
|
104
174
|
def upload(local, remote)
|
105
|
-
|
175
|
+
log(:debug) { "upload(#{local.inspect}, #{remote.inspect})" }
|
176
|
+
log(:debug) { "config(#{@config.inspect})" }
|
177
|
+
|
178
|
+
@sftp ||= Net::SFTP.start(@config.host_name, @config.user, ssh_options)
|
179
|
+
log(:debug) { "sftp(#{@sftp.inspect})" }
|
106
180
|
|
107
|
-
@config.logger and @config.logger.debug { "config(#{@config.ssh.inspect})" }
|
108
|
-
@config.logger and @config.logger.info { "parameters(#{local},#{remote})" }
|
109
181
|
@sftp.upload!(local.to_s, remote.to_s) do |event, uploader, *args|
|
110
182
|
case event
|
111
183
|
when :open
|
112
|
-
|
184
|
+
log(:info) { "upload(#{args[0].local} -> #{args[0].remote})" }
|
113
185
|
when :close
|
114
|
-
|
186
|
+
log(:debug) { "close(#{args[0].remote})" }
|
115
187
|
when :mkdir
|
116
|
-
|
188
|
+
log(:debug) { "mkdir(#{args[0]})" }
|
117
189
|
when :put
|
118
|
-
|
190
|
+
log(:debug) { "put(#{args[0].remote}, size #{args[2].size} bytes, offset #{args[1]})" }
|
119
191
|
when :finish
|
120
|
-
|
192
|
+
log(:info) { "finish" }
|
121
193
|
end
|
122
194
|
end
|
123
195
|
|
124
196
|
true
|
125
197
|
end
|
126
198
|
|
127
|
-
|
128
|
-
|
199
|
+
# Downloads a remote file to the local host.
|
200
|
+
#
|
201
|
+
# @param [String] remote The remote file/path you with to download from.
|
202
|
+
# @param [String] local The local file/path you wish to download to.
|
203
|
+
#
|
204
|
+
# @example Download a file:
|
205
|
+
# $logger = ZTK::Logger.new(STDOUT)
|
206
|
+
# ssh = ZTK::SSH.new
|
207
|
+
# ssh.config do |config|
|
208
|
+
# config.user = ENV["USER"]
|
209
|
+
# config.host_name = "127.0.0.1"
|
210
|
+
# end
|
211
|
+
# local = File.expand_path(File.join("/tmp", "id_rsa.pub"))
|
212
|
+
# remote = File.expand_path(File.join(ENV["HOME"], ".ssh", "id_rsa.pub"))
|
213
|
+
# ssh.download(remote, local)
|
129
214
|
def download(remote, local)
|
130
|
-
|
215
|
+
log(:debug) { "download(#{remote.inspect}, #{local.inspect})" }
|
216
|
+
log(:debug) { "config(#{@config.inspect})" }
|
217
|
+
|
218
|
+
@sftp ||= Net::SFTP.start(@config.host_name, @config.user, ssh_options)
|
219
|
+
log(:debug) { "sftp(#{@sftp.inspect})" }
|
131
220
|
|
132
|
-
@config.logger and @config.logger.debug { "config(#{@config.ssh.inspect})" }
|
133
|
-
@config.logger and @config.logger.info { "parameters(#{remote},#{local})" }
|
134
221
|
@sftp.download!(remote.to_s, local.to_s) do |event, downloader, *args|
|
135
222
|
case event
|
136
223
|
when :open
|
137
|
-
|
224
|
+
log(:info) { "download(#{args[0].remote} -> #{args[0].local})" }
|
138
225
|
when :close
|
139
|
-
|
226
|
+
log(:debug) { "close(#{args[0].local})" }
|
140
227
|
when :mkdir
|
141
|
-
|
228
|
+
log(:debug) { "mkdir(#{args[0]})" }
|
142
229
|
when :get
|
143
|
-
|
230
|
+
log(:debug) { "get(#{args[0].remote}, size #{args[2].size} bytes, offset #{args[1]})" }
|
144
231
|
when :finish
|
145
|
-
|
232
|
+
log(:info) { "finish" }
|
146
233
|
end
|
147
234
|
end
|
148
235
|
|
@@ -150,53 +237,98 @@ module ZTK
|
|
150
237
|
end
|
151
238
|
|
152
239
|
|
153
|
-
################################################################################
|
154
240
|
private
|
155
|
-
################################################################################
|
156
241
|
|
242
|
+
# Builds our SSH console command.
|
243
|
+
def console_command
|
244
|
+
log(:debug) { "console_command" }
|
245
|
+
log(:debug) { "config(#{@config.inspect})" }
|
246
|
+
|
247
|
+
command = [ "ssh" ]
|
248
|
+
command << [ "-q" ]
|
249
|
+
command << [ "-A" ]
|
250
|
+
command << [ "-o", "UserKnownHostsFile=/dev/null" ]
|
251
|
+
command << [ "-o", "StrictHostKeyChecking=no" ]
|
252
|
+
command << [ "-o", "KeepAlive=yes" ]
|
253
|
+
command << [ "-o", "ServerAliveInterval=60" ]
|
254
|
+
command << [ "-i", @config.keys ] if @config.keys
|
255
|
+
command << [ "-o", "ProxyCommand=\"#{proxy_command}\"" ] if @config.proxy_host_name
|
256
|
+
command << "#{@config.user}@#{@config.host_name}"
|
257
|
+
command = command.flatten.compact.join(" ")
|
258
|
+
log(:debug) { "console_command(#{command.inspect})" }
|
259
|
+
command
|
260
|
+
end
|
261
|
+
|
262
|
+
# Builds our SSH proxy command.
|
157
263
|
def proxy_command
|
158
|
-
|
264
|
+
log(:debug) { "proxy_command" }
|
265
|
+
log(:debug) { "config(#{@config.inspect})" }
|
159
266
|
|
160
|
-
if !@config.
|
161
|
-
message = "You must specify an
|
162
|
-
|
267
|
+
if !@config.proxy_user
|
268
|
+
message = "You must specify an proxy user in order to SSH proxy."
|
269
|
+
log(:fatal) { message }
|
270
|
+
raise SSHError, message
|
271
|
+
end
|
272
|
+
|
273
|
+
if !@config.proxy_host_name
|
274
|
+
message = "You must specify an proxy host_name in order to SSH proxy."
|
275
|
+
log(:fatal) { message }
|
163
276
|
raise SSHError, message
|
164
277
|
end
|
165
278
|
|
166
279
|
command = ["ssh"]
|
167
280
|
command << [ "-q" ]
|
281
|
+
command << [ "-A" ]
|
168
282
|
command << [ "-o", "UserKnownHostsFile=/dev/null" ]
|
169
283
|
command << [ "-o", "StrictHostKeyChecking=no" ]
|
170
284
|
command << [ "-o", "KeepAlive=yes" ]
|
171
285
|
command << [ "-o", "ServerAliveInterval=60" ]
|
172
|
-
command << [ "-i", @config.
|
173
|
-
command << "#{@config.
|
286
|
+
command << [ "-i", @config.proxy_keys ] if @config.proxy_keys
|
287
|
+
command << "#{@config.proxy_user}@#{@config.proxy_host_name}"
|
174
288
|
command << "nc %h %p"
|
175
289
|
command = command.flatten.compact.join(" ")
|
176
|
-
|
290
|
+
log(:debug) { "proxy_command(#{command.inspect})" }
|
177
291
|
command
|
178
292
|
end
|
179
293
|
|
180
|
-
|
181
|
-
|
294
|
+
# Builds our SSH options hash.
|
182
295
|
def ssh_options
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
options
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
296
|
+
log(:debug) { "ssh_options" }
|
297
|
+
log(:debug) { "config(#{@config.inspect})" }
|
298
|
+
|
299
|
+
options = {
|
300
|
+
:forward_agent => true,
|
301
|
+
:compression => false,
|
302
|
+
:user_known_hosts_file => '/dev/null'
|
303
|
+
}
|
304
|
+
|
305
|
+
# These are plainly documented on the Net::SSH config class.
|
306
|
+
options.merge!(:encryption => @config.encryption) if @config.encryption
|
307
|
+
options.merge!(:compression => @config.compression) if @config.compression
|
308
|
+
options.merge!(:compression_level => @config.compression_level) if @config.compression_level
|
309
|
+
options.merge!(:timeout => @config.timeout) if @config.timeout
|
310
|
+
options.merge!(:forward_agent => @config.forward_agent) if @config.forward_agent
|
311
|
+
options.merge!(:global_known_hosts_file => @config.global_known_hosts_file) if @config.global_known_hosts_file
|
312
|
+
options.merge!(:auth_methods => @config.auth_methods) if @config.auth_methods
|
313
|
+
options.merge!(:host_key => @config.host_key) if @config.host_key
|
314
|
+
options.merge!(:host_key_alias => @config.host_key_alias) if @config.host_key_alias
|
315
|
+
options.merge!(:host_name => @config.host_name) if @config.host_name
|
316
|
+
options.merge!(:keys => @config.keys) if @config.keys
|
317
|
+
options.merge!(:keys_only => @config.keys_only) if @config.keys_only
|
318
|
+
options.merge!(:hmac => @config.hmac) if @config.hmac
|
319
|
+
options.merge!(:port => @config.port) if @config.port
|
320
|
+
options.merge!(:proxy => Net::SSH::Proxy::Command.new(proxy_command)) if @config.proxy_host_name
|
321
|
+
options.merge!(:rekey_limit => @config.rekey_limit) if @config.rekey_limit
|
322
|
+
options.merge!(:user => @config.user) if @config.user
|
323
|
+
options.merge!(:user_known_hosts_file => @config.user_known_hosts_file) if @config.user_known_hosts_file
|
324
|
+
|
325
|
+
# This is not plainly documented on the Net::SSH config class.
|
326
|
+
options.merge!(:password => @config.password) if @config.password
|
327
|
+
|
328
|
+
log(:debug) { "ssh_options(#{options.inspect})" }
|
191
329
|
options
|
192
330
|
end
|
193
331
|
|
194
|
-
################################################################################
|
195
|
-
|
196
332
|
end
|
197
333
|
|
198
|
-
################################################################################
|
199
|
-
|
200
334
|
end
|
201
|
-
|
202
|
-
################################################################################
|