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 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
- ################################################################################