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 CHANGED
@@ -1,4 +1,5 @@
1
1
  *.log
2
+ *.txt
2
3
  *.gem
3
4
  *.rbc
4
5
  .rvmrc
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"
@@ -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
- @config.logger and @config.logger.debug{ "config(#{@config.inspect})" }
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
- ################################################################################
@@ -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
+ ################################################################################
@@ -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, Vers::IOn 2.0
5
+ # License: Apache License, VersIOn 2.0
6
6
  #
7
- # Licensed under the Apache License, Vers::IOn 2.0 (the "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 CONDIT::IONS OF ANY KIND, either express or implied.
16
- # See the License for the specific language governing permiss::IOns and
17
- # limitat::IOns under the License.
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 Parallel < ::ZTK::Base
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 = ::Array.new
45
- @results = ::Array.new
46
- ::GC.respond_to?(:copy_on_write_friendly=) and ::GC.copy_on_write_friendly = true
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(*args)
52
- @config.logger and @config.logger.debug{ "forks(#{@forks.inspect})" }
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 = ::IO.pipe
59
- parent_reader, child_writer = ::IO.pipe
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(::Process.pid)
62
- pid = ::Process.fork do
63
- @config.after_fork and @config.after_fork.call(::Process.pid)
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 = yield).nil?
69
- @config.logger and @config.logger.debug{ "write(#{data.inspect})" }
70
- child_writer.write(::Base64.encode64(::Marshal.dump(data)))
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
- ::Process.exit!(0)
81
+ Process.exit!(0)
76
82
  end
77
- @config.after_fork and @config.after_fork.call(::Process.pid)
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
- @config.logger and @config.logger.debug{ "forks(#{@forks.inspect})" }
92
- pid, status = (::Process.wait2(-1, ::Process::WNOHANG) rescue nil)
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 = (::Marshal.load(::Base64.decode64(fork[:reader].read.to_s)) rescue nil)
95
- @config.logger and @config.logger.debug{ "read(#{data.inspect})" }
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 = ::Array.new
115
+ data = Array.new
111
116
  while @forks.count > 0
112
- data << self.wait
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
- @config.logger and @config.logger.debug{ "count(#{@forks.count})" }
126
+ log(:debug) { "count(#{@forks.count})" }
121
127
  @forks.count
122
128
  end
123
129
 
@@ -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
+ ################################################################################
@@ -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
- class SSH < ::ZTK::Base
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
- @config.logger and @config.logger.debug { "config(#{@config.ssh.inspect})" }
100
+ log(:debug) { "console" }
101
+ log(:debug) { "config(#{@config.inspect})" }
50
102
 
51
- command = [ "ssh" ]
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
- @ssh ||= ::Net::SSH.start(@config.ssh.host, @config.ssh.user, ssh_options)
124
+ log(:debug) { "exec(#{command.inspect}, #{options.inspect})" }
125
+ log(:debug) { "config(#{@config.inspect})" }
69
126
 
70
- options = { :silence => false }.merge(options)
71
- silence = options[:silence]
72
- output = ""
127
+ @ssh ||= Net::SSH.start(@config.host_name, @config.user, ssh_options)
128
+ log(:debug) { "ssh(#{@ssh.inspect})" }
73
129
 
74
- @config.logger and @config.logger.debug { "config(#{@config.ssh.inspect})" }
75
- @config.logger and @config.logger.debug { "options(#{options.inspect})" }
76
- @config.logger and @config.logger.info { "command(#{command})" }
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
- @config.logger and @config.logger.debug { "channel opened" }
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
- output += data
84
- @config.logger and @config.logger.debug { data.chomp.strip }
85
- @config.stdout.print(data) if !silence
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
- output += data
90
- @config.logger and @config.logger.debug { data.chomp.strip }
91
- @config.stderr.print(data) if !silence
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
- @config.logger and @config.logger.debug { "channel closed" }
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
- @sftp ||= ::Net::SFTP.start(@config.ssh.host, @config.ssh.user, ssh_options)
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
- @config.logger and @config.logger.info { "upload(#{args[0].local} -> #{args[0].remote})" }
184
+ log(:info) { "upload(#{args[0].local} -> #{args[0].remote})" }
113
185
  when :close
114
- @config.logger and @config.logger.debug { "close(#{args[0].remote})" }
186
+ log(:debug) { "close(#{args[0].remote})" }
115
187
  when :mkdir
116
- @config.logger and @config.logger.debug { "mkdir(#{args[0]})" }
188
+ log(:debug) { "mkdir(#{args[0]})" }
117
189
  when :put
118
- @config.logger and @config.logger.debug { "put(#{args[0].remote}, size #{args[2].size} bytes, offset #{args[1]})" }
190
+ log(:debug) { "put(#{args[0].remote}, size #{args[2].size} bytes, offset #{args[1]})" }
119
191
  when :finish
120
- @config.logger and @config.logger.info { "finish" }
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
- @sftp ||= ::Net::SFTP.start(@config.ssh.host, @config.ssh.user, ssh_options)
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
- @config.logger and @config.logger.info { "download(#{args[0].remote} -> #{args[0].local})" }
224
+ log(:info) { "download(#{args[0].remote} -> #{args[0].local})" }
138
225
  when :close
139
- @config.logger and @config.logger.debug { "close(#{args[0].local})" }
226
+ log(:debug) { "close(#{args[0].local})" }
140
227
  when :mkdir
141
- @config.logger and @config.logger.debug { "mkdir(#{args[0]})" }
228
+ log(:debug) { "mkdir(#{args[0]})" }
142
229
  when :get
143
- @config.logger and @config.logger.debug { "get(#{args[0].remote}, size #{args[2].size} bytes, offset #{args[1]})" }
230
+ log(:debug) { "get(#{args[0].remote}, size #{args[2].size} bytes, offset #{args[1]})" }
144
231
  when :finish
145
- @config.logger and @config.logger.info { "finish" }
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
- @config.logger and @config.logger.debug { "config(#{@config.ssh.inspect})" }
264
+ log(:debug) { "proxy_command" }
265
+ log(:debug) { "config(#{@config.inspect})" }
159
266
 
160
- if !@config.ssh.identity_file
161
- message = "You must specify an identity file in order to SSH proxy."
162
- @config.logger and @config.logger.fatal { message }
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.ssh.proxy_identity_file ] if @config.ssh.proxy_identity_file
173
- command << "#{@config.ssh.proxy_user}@#{@config.ssh.proxy_host}"
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
- @config.logger and @config.logger.debug { "command(#{command})" }
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
- @config.logger and @config.logger.debug { "config(#{@config.ssh.inspect})" }
184
- options = {}
185
- options.merge!(:password => @config.ssh.password) if @config.ssh.password
186
- options.merge!(:keys => @config.ssh.identity_file) if @config.ssh.identity_file
187
- options.merge!(:timeout => @config.ssh.timeout) if @config.ssh.timeout
188
- options.merge!(:user_known_hosts_file => '/dev/null') if !@config.ssh.host_key_verify
189
- options.merge!(:proxy => ::Net::SSH::Proxy::Command.new(proxy_command)) if @config.ssh.proxy
190
- @config.logger and @config.logger.debug { "options(#{options.inspect})" }
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
- ################################################################################