25 ### port: (optional) The port to connect to (defaults to 22) |
25 ### port: (optional) The port to connect to (defaults to 22) |
26 ### user: (optional) The user to connect as (defaults to root) |
26 ### user: (optional) The user to connect as (defaults to root) |
27 ### key: (optional) The path to an SSH private key |
27 ### key: (optional) The path to an SSH private key |
28 ### attributes: (optional) Additional data to attach to the template |
28 ### attributes: (optional) Additional data to attach to the template |
29 ### nocleanup: (optional) Leave the remote script after execution? (default to false) |
29 ### nocleanup: (optional) Leave the remote script after execution? (default to false) |
30 ### tempdir: (optional) The destination temp directory. (defaults to /tmp) |
30 ### delete_cmd: (optional) The command to delete the remote script. (default to 'rm') |
|
31 ### run_binary: (optional) Windows doesn't allow direct execution of scripts, this is prefixed to the remote command if present. |
|
32 ### tempdir: (optional) The destination temp directory. (defaults to /tmp/, needs to include the separator character) |
31 ### |
33 ### |
32 ### |
34 ### |
33 ### Additionally, this class responds to the 'symphony.ssh' configurability |
35 ### Additionally, this class responds to the 'symphony.ssh' configurability |
34 ### key. Currently, you can override the default ssh user and private key. |
36 ### key. Currently, you can override the default ssh user and private key. |
35 ### |
37 ### |
77 ### execute it. |
79 ### execute it. |
78 ### |
80 ### |
79 def work( payload, metadata ) |
81 def work( payload, metadata ) |
80 template = payload[ 'template' ] |
82 template = payload[ 'template' ] |
81 attributes = payload[ 'attributes' ] || {} |
83 attributes = payload[ 'attributes' ] || {} |
82 port = payload[ 'port' ] || 22 |
|
83 user = payload[ 'user' ] || Symphony::Task::SSH.user |
84 user = payload[ 'user' ] || Symphony::Task::SSH.user |
84 key = payload[ 'key' ] || Symphony::Task::SSH.key |
85 key = payload[ 'key' ] || Symphony::Task::SSH.key |
85 nocleanup = payload[ 'nocleanup' ] |
86 tempdir = payload[ 'tempdir' ] || '/tmp/' |
86 tempdir = payload[ 'tempdir' ] || '/tmp' |
|
87 |
87 |
88 raise ArgumentError, "Missing required option 'template'" unless template |
88 raise ArgumentError, "Missing required option 'template'" unless template |
89 raise ArgumentError, "Missing required option 'host'" unless payload[ 'host' ] |
89 raise ArgumentError, "Missing required option 'host'" unless payload[ 'host' ] |
90 |
90 |
91 remote_filename = self.make_remote_filename( template, tempdir ) |
91 remote_filename = self.make_remote_filename( template, tempdir ) |
92 source = self.generate_script( template, attributes ) |
92 source = self.generate_script( template, attributes ) |
93 |
93 |
94 ssh_options = DEFAULT_SSH_OPTIONS.merge( port: port, keys: Array(key) ) |
94 # Map any configuration parameters in the payload to ssh |
|
95 # options, for potential per-message behavior overrides. |
|
96 ssh_opts_override = payload. |
|
97 slice( *DEFAULT_SSH_OPTIONS.keys.map( &:to_s ) ). |
|
98 transform_keys{|k| k.to_sym } |
|
99 |
|
100 ssh_options = DEFAULT_SSH_OPTIONS.dup.merge!( |
|
101 ssh_opts_override, |
|
102 port: payload[ 'port' ] || 22, |
|
103 keys: Array( key ) |
|
104 ) |
95 ssh_options.merge!( |
105 ssh_options.merge!( |
96 logger: Loggability[ Net::SSH ], |
106 logger: Loggability[ Net::SSH ], |
97 verbose: :debug |
107 verbose: :debug |
98 ) if payload[ 'debug' ] |
108 ) if payload[ 'debug' ] |
99 |
109 |
101 self.log.debug "Uploading script (%d bytes) to %s:%s." % |
111 self.log.debug "Uploading script (%d bytes) to %s:%s." % |
102 [ source.bytesize, payload['host'], remote_filename ] |
112 [ source.bytesize, payload['host'], remote_filename ] |
103 self.upload_script( conn, source, remote_filename ) |
113 self.upload_script( conn, source, remote_filename ) |
104 self.log.debug " done with the upload." |
114 self.log.debug " done with the upload." |
105 |
115 |
106 self.run_script( conn, remote_filename, nocleanup ) |
116 self.run_script( conn, remote_filename, payload ) |
107 self.log.debug "Output was:\n#{@output}" |
117 self.log.debug "Output was:\n#{@output}" |
108 end |
118 end |
109 |
119 |
110 return true |
120 return true |
111 end |
121 end |
116 ######### |
126 ######### |
117 |
127 |
118 ### Generate a unique filename for the script on the remote host, |
128 ### Generate a unique filename for the script on the remote host, |
119 ### based on +template+ name. |
129 ### based on +template+ name. |
120 ### |
130 ### |
121 def make_remote_filename( template, tempdir="/tmp" ) |
131 def make_remote_filename( template, tempdir="/tmp/" ) |
122 basename = File.basename( template, File.extname(template) ) |
132 basename = File.basename( template, File.extname(template) ) |
123 tmpname = "%s/%s-%s" % [ |
133 tmpname = "%s%s-%s" % [ |
124 tempdir, |
134 tempdir, |
125 basename, |
135 basename, |
126 SecureRandom.hex( 6 ) |
136 SecureRandom.hex( 6 ) |
127 ] |
137 ] |
128 |
138 |
151 end |
161 end |
152 end |
162 end |
153 |
163 |
154 |
164 |
155 ### Run the +remote_filename+ via the ssh +conn+. The script |
165 ### Run the +remote_filename+ via the ssh +conn+. The script |
156 ### will be deleted automatically unless +nocleanup+ is true. |
166 ### will be deleted automatically unless +nocleanup+ is set |
|
167 ### in the payload. |
157 ### |
168 ### |
158 def run_script( conn, remote_filename, nocleanup=false ) |
169 def run_script( conn, remote_filename, payload ) |
159 @output = conn.exec!( remote_filename ) |
170 delete_cmd = payload[ 'delete_cmd' ] || 'rm' |
160 conn.exec!( "rm #{remote_filename}" ) unless nocleanup |
171 command = remote_filename |
|
172 command = "%s %s" % [ payload['run_binary'], remote_filename ] if payload[ 'run_binary' ] |
|
173 |
|
174 @output = conn.exec!( command ) |
|
175 conn.exec!( "#{delete_cmd} #{remote_filename}" ) unless payload[ 'nocleanup' ] |
161 end |
176 end |
162 |
177 |
163 end # Symphony::Task::SSHScript |
178 end # Symphony::Task::SSHScript |
164 |
179 |