17 ### |
17 ### |
18 ### host: (required) The hostname to connect to |
18 ### host: (required) The hostname to connect to |
19 ### command: (required) The command to run on the remote host |
19 ### command: (required) The command to run on the remote host |
20 ### port: (optional) The port to connect to (defaults to 22) |
20 ### port: (optional) The port to connect to (defaults to 22) |
21 ### opts: (optional) Explicit SSH client options |
21 ### opts: (optional) Explicit SSH client options |
|
22 ### env: (optional) A hash of environment vars to set for the connection. |
22 ### user: (optional) The user to connect as (defaults to root) |
23 ### user: (optional) The user to connect as (defaults to root) |
23 ### key: (optional) The path to an SSH private key |
24 ### key: (optional) The path to an SSH private key |
24 ### |
25 ### |
25 ### |
26 ### |
26 ### Additionally, this class responds to the 'symphony.ssh' configurability |
27 ### Additionally, this class responds to the 'symphony.ssh' configurability |
84 # An absolute path to a password-free ssh private key. |
85 # An absolute path to a password-free ssh private key. |
85 setting :key |
86 setting :key |
86 end |
87 end |
87 |
88 |
88 |
89 |
89 ### Perform the ssh connection, passing the command to the pipe |
90 ### Perform the ssh connection in 'exec' mode, and retrieve any |
90 ### and retreiving any output from the remote end. |
91 ### output from the remote end. |
91 ### |
92 ### |
92 def work( payload, metadata ) |
93 def work( payload, metadata ) |
93 command = payload[ 'command' ] |
94 raise ArgumentError, "Missing required option 'command'" unless payload[ 'command' ] |
94 raise ArgumentError, "Missing required option 'command'" unless command |
|
95 raise ArgumentError, "Missing required option 'host'" unless payload[ 'host' ] |
95 raise ArgumentError, "Missing required option 'host'" unless payload[ 'host' ] |
96 |
96 |
97 exitcode = self.open_connection( payload, metadata ) do |reader, writer| |
97 exitcode = self.open_connection( payload, metadata ) do |reader, writer| |
98 self.log.debug "Writing command #{command}..." |
98 #self.log.debug "Writing command #{command}..." |
99 writer.puts( command ) |
99 #writer.puts( command ) |
100 self.log.debug " closing child's writer." |
100 self.log.debug " closing child's writer." |
101 writer.close |
101 writer.close |
102 self.log.debug " reading from child." |
102 self.log.debug " reading from child." |
103 reader.read |
103 reader.read |
104 end |
104 end |
121 |
121 |
122 port = payload[ 'port' ] || 22 |
122 port = payload[ 'port' ] || 22 |
123 opts = payload[ 'opts' ] || Symphony::Task::SSH.opts |
123 opts = payload[ 'opts' ] || Symphony::Task::SSH.opts |
124 user = payload[ 'user' ] || Symphony::Task::SSH.user |
124 user = payload[ 'user' ] || Symphony::Task::SSH.user |
125 key = payload[ 'key' ] || Symphony::Task::SSH.key |
125 key = payload[ 'key' ] || Symphony::Task::SSH.key |
|
126 env = payload[ 'env' ] || {} |
126 |
127 |
127 cmd = [] |
128 cmd = [] |
128 cmd << Symphony::Task::SSH.path |
129 cmd << Symphony::Task::SSH.path |
129 cmd += opts |
130 cmd += opts |
130 |
131 |
131 cmd << '-p' << port.to_s |
132 cmd << '-p' << port.to_s |
132 cmd << '-i' << key if key |
133 cmd << '-i' << key if key |
133 cmd << '-l' << user |
134 cmd << '-l' << user |
134 cmd << payload[ 'host' ] |
135 cmd << payload[ 'host' ] |
|
136 cmd << payload[ 'command' ] |
135 cmd.flatten! |
137 cmd.flatten! |
136 self.log.debug "Running SSH command with: %p" % [ Shellwords.shelljoin(cmd) ] |
138 self.log.debug "Running SSH command with: %p" % [ Shellwords.shelljoin(cmd) ] |
137 |
139 |
138 parent_reader, child_writer = IO.pipe |
140 parent_reader, child_writer = IO.pipe |
139 child_reader, parent_writer = IO.pipe |
141 child_reader, parent_writer = IO.pipe |
140 |
142 |
141 pid = Process.spawn( *cmd, :out => child_writer, :in => child_reader, :close_others => true ) |
143 pid = Process.spawn( env, *cmd, |
|
144 out: child_writer, |
|
145 in: child_reader, |
|
146 close_others: true, |
|
147 unsetenv_others: true |
|
148 ) |
|
149 |
142 child_writer.close |
150 child_writer.close |
143 child_reader.close |
151 child_reader.close |
144 |
152 |
145 self.log.debug "Yielding back to the run block." |
153 self.log.debug "Yielding back to the run block." |
146 @output = yield( parent_reader, parent_writer ) |
154 @output = yield( parent_reader, parent_writer ) |
147 @output = @output.split("\n").reject{|l| l =~ SSH_CLEANUP }.join |
155 @output = @output.split( /\r?\n/ ).reject{|l| l =~ SSH_CLEANUP }.join |
148 self.log.debug " run block done." |
156 self.log.debug " run block done." |
149 |
157 |
150 status = nil |
158 rescue => err |
151 |
159 self.log.error( err.message ) |
152 ensure |
160 ensure |
153 if pid |
161 if pid |
154 active = Process.kill( 0, pid ) rescue false |
162 active = Process.kill( 0, pid ) rescue false |
155 Process.kill( :TERM, pid ) if active |
163 Process.kill( :TERM, pid ) if active |
156 pid, status = Process.waitpid2( pid ) |
164 pid, status = Process.waitpid2( pid ) |