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