Class: Furnish::Provisioner::SSH
- Inherits:
-
API
- Object
- API
- Furnish::Provisioner::SSH
- Includes:
- Logger::Mixins
- Defined in:
- lib/furnish/provisioners/ssh.rb
Overview
Provisioner to execute SSH commands on remote targets during startup and shutdown. See ::new for construction requirements.
Please see Net::SSH::Verifiers::NoSaveStrict and #paranoid about how host keys are handled in a surprising way.
- Startup
-
requires:
- ips (Set<String>)
-
list of IP addresses to target
-
yields:
- ssh_exit_statuses (Hash<String, Integer>)
-
IP -> exit status map
- ssh_output (Hash<String, String>)
-
IP -> command output map
-
- Shutdown
-
accepts:
- ips (Set<String>)
-
list of IP addresses to target
-
yields:
- ssh_exit_statuses (Hash<String, Integer>)
-
IP -> exit status map
- ssh_output (Hash<String, String>)
-
IP -> command output map
-
Instance Attribute Summary collapse
-
#ips ⇒ Object
readonly
a stored list of the IPs dealt with by this provisioner.
-
#output ⇒ Object
readonly
a hash of ip -> output, accessible after provision.
Instance Method Summary collapse
-
#check_auth_args ⇒ Object
Predicate for determining requirements for ::new.
-
#check_command_args ⇒ Object
Predicate for determining requirements for ::new.
-
#check_stdin_pty ⇒ Object
Predicate for determining requirements for ::new.
-
#initialize(args) ⇒ SSH
constructor
Construct the SSH provisioner.
-
#log(host, output) ⇒ Object
Checks #log_output and logs the output with the host if set.
-
#log_output ⇒ Object
:attr: log_output.
-
#merge_output ⇒ Object
:attr: merge_output.
-
#mute_output ⇒ Object
:attr: mute_output.
-
#noop ⇒ Object
What happens when we can’t execute something (e.g., because of a missing command).
-
#paranoid ⇒ Object
:attr: paranoid.
-
#password ⇒ Object
:attr: password.
-
#private_key ⇒ Object
:attr: private_key.
-
#private_key_path ⇒ Object
:attr: private_key_path.
-
#provision_wait ⇒ Object
:attr: provision_wait.
-
#report ⇒ Object
Outputs the commands if they exist.
-
#require_pty ⇒ Object
:attr: require_pty.
-
#run_ssh_provision(provision_command) ⇒ Object
Runs multiple ssh commands in threads, monitors those threads and stuffs status information.
-
#shutdown(args = {}) ⇒ Object
Deprovision: run the command.
-
#shutdown_command ⇒ Object
:attr: shutdown_command.
-
#ssh(host, cmd) ⇒ Object
Performs the actual connection and execution.
-
#ssh_options ⇒ Object
Constructs the proper hash for Net::SSH.start options.
-
#startup(args = {}) ⇒ Object
Provision: run the command on all hosts and return the status from #run_ssh_provision.
-
#startup_command ⇒ Object
:attr: startup_command.
-
#stdin ⇒ Object
:attr: stdin.
-
#success ⇒ Object
:attr: success.
-
#username ⇒ Object
:attr: username.
Constructor Details
#initialize(args) ⇒ SSH
Construct the SSH provisioner.
- Requirements
-
#username must be provided.
-
#password, #private_key, or #private_key_path must be provided, but only one of them.
-
#startup_command or #shutdown_command must be provided. You may provide both.
-
#stdin and #require_pty cannot be provided together.
-
226 227 228 229 230 231 232 233 234 235 |
# File 'lib/furnish/provisioners/ssh.rb', line 226 def initialize(args) super check_auth_args check_command_args check_stdin_pty @paranoid = args.has_key?(:paranoid) ? args[:paranoid] : :no_save_strict @provision_wait ||= 300 @success ||= Set[0] end |
Instance Attribute Details
#ips ⇒ Object (readonly)
a stored list of the IPs dealt with by this provisioner
206 207 208 |
# File 'lib/furnish/provisioners/ssh.rb', line 206 def ips @ips end |
#output ⇒ Object (readonly)
a hash of ip -> output, accessible after provision. overwritten on both startup and shutdown.
210 211 212 |
# File 'lib/furnish/provisioners/ssh.rb', line 210 def output @output end |
Instance Method Details
#check_auth_args ⇒ Object
Predicate for determining requirements for ::new.
258 259 260 261 262 263 264 265 266 267 268 269 270 |
# File 'lib/furnish/provisioners/ssh.rb', line 258 def check_auth_args unless username raise ArgumentError, "username must be provided" end unless password or private_key or private_key_path raise ArgumentError, "password, private_key, or private_key_path must be provided" end if [password, private_key, private_key_path].compact.count > 1 raise ArgumentError, "You may only supply one of password, private_key, or private_key_path." end end |
#check_command_args ⇒ Object
Predicate for determining requirements for ::new.
249 250 251 252 253 |
# File 'lib/furnish/provisioners/ssh.rb', line 249 def check_command_args unless startup_command or shutdown_command raise ArgumentError, "startup_command or shutdown_command must be provided at minimum." end end |
#check_stdin_pty ⇒ Object
Predicate for determining requirements for ::new.
240 241 242 243 244 |
# File 'lib/furnish/provisioners/ssh.rb', line 240 def check_stdin_pty if stdin and require_pty raise ArgumentError, "stdin and require_pty are incompatible -- if used together, will hang the provision." end end |
#log(host, output) ⇒ Object
Checks #log_output and logs the output with the host if set.
275 276 277 278 279 280 281 282 |
# File 'lib/furnish/provisioners/ssh.rb', line 275 def log(host, output) if log_output if_debug do print "[#{host}] #{output}" flush end end end |
#log_output ⇒ Object
:attr: log_output
If true, will send all output to the furnish logger. Use #merge_output to get stderr as well.
171 172 |
# File 'lib/furnish/provisioners/ssh.rb', line 171 furnish_property :log_output, "If true, will send all output to the furnish logger. Use merge_output to get stderr as well." |
#merge_output ⇒ Object
:attr: merge_output
If true, will merge stdout and stderr for purposes of output.
162 163 |
# File 'lib/furnish/provisioners/ssh.rb', line 162 furnish_property :merge_output, "If true, will merge stdout and stderr for purposes of output." |
#mute_output ⇒ Object
:attr: mute_output
If true, output will not be stored or relayed. Useful for commands which will perform lots of output. log_output is not affected.“
192 193 |
# File 'lib/furnish/provisioners/ssh.rb', line 192 furnish_property :mute_output, "If true, output will not be stored or relayed. Useful for commands which will perform lots of output. log_output is not affected." |
#noop ⇒ Object
What happens when we can’t execute something (e.g., because of a missing command). Just some boilerplate values.
433 434 435 436 437 438 |
# File 'lib/furnish/provisioners/ssh.rb', line 433 def noop exit_statuses = Hash[ips.map { |ip| [ip, 0] }] @output = Hash[ips.map { |ip| [ip, ""] }] return({ :ssh_exit_statuses => exit_statuses, :ssh_output => output }) end |
#paranoid ⇒ Object
:attr: paranoid
Maps to Net::SSH.start’s :paranoid option. If :no_save_strict is assigned (the default), will use our Net::SSH::Verifiers::NoSaveStrict verifier which will not attempt to save any host keys that we do not recognize. Any that do exist however will be checked appropriately.
183 184 |
# File 'lib/furnish/provisioners/ssh.rb', line 183 furnish_property :paranoid, "Maps to Net::SSH.start's :paranoid option, used for host key validation. Use :no_save_strict (the default) to get something similar to :strict that doesn't save on an unknown key." |
#password ⇒ Object
:attr: password
Password to use when authenticating. Optional, but this or #private_key or #private_key_path must be provided.
93 94 95 |
# File 'lib/furnish/provisioners/ssh.rb', line 93 furnish_property :password, "Password to use when authenticating. Optional, but this or private_key or private_key_path must be provided.", String |
#private_key ⇒ Object
:attr: private_key
Private key to use when authenticating. Optional, but this or #password or #private_key_path must be provided.
103 104 105 |
# File 'lib/furnish/provisioners/ssh.rb', line 103 furnish_property :private_key, "Private key to use when authenticating. Optional, but this or password or private_key_path must be provided.", String |
#private_key_path ⇒ Object
:attr: private_key_path
Path to file on disk containing the private key to use when authenticating. Optional, but this or #password or #private_key must be provided.
114 115 116 |
# File 'lib/furnish/provisioners/ssh.rb', line 114 furnish_property :private_key_path, "Path to file on disk containing the private key to use when authenticating. Optional, but this or password or private_key must be provided.", String |
#provision_wait ⇒ Object
:attr: provision_wait
How long to wait before giving up on SSH returning. Default is 300 seconds. Fractional values OK.
74 75 76 |
# File 'lib/furnish/provisioners/ssh.rb', line 74 furnish_property :provision_wait, "How long to wait before giving up on SSH returning. Default is 300 seconds. Fractional values OK.", Numeric |
#report ⇒ Object
Outputs the commands if they exist.
467 468 469 470 471 472 473 474 475 476 477 478 479 |
# File 'lib/furnish/provisioners/ssh.rb', line 467 def report a = [] if startup_command a.push("startup: '#{startup_command}'") end if shutdown_command a.push("shutdown: '#{shutdown_command}'") end return a end |
#require_pty ⇒ Object
:attr: require_pty
If true, attempts to allocate a pty after connecting. If this fails, fails the provision. Default is false. Cannot be used with #stdin.
144 145 |
# File 'lib/furnish/provisioners/ssh.rb', line 144 furnish_property :require_pty, "If true, attempts to allocate a pty after connecting. If this fails, fails the provision. Default is false. Cannot be used with stdin." |
#run_ssh_provision(provision_command) ⇒ Object
Runs multiple ssh commands in threads, monitors those threads and stuffs status information. Will return #noop unless a command is provided. Called by #startup and #shutdown.
384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 |
# File 'lib/furnish/provisioners/ssh.rb', line 384 def run_ssh_provision(provision_command) unless provision_command return noop end # # XXX sorry for the ugly. creates a IP => Thread map for tracking return values. # thread_map = Hash[ ips.map do |ip| [ ip, Thread.new do ssh(ip, provision_command) end ] end ] # FIXME see TODO about output handling exit_statuses = { } @output = { } begin Timeout.timeout(provision_wait) do thread_map.each do |ip, thr| result = thr.value # exception will happen here. output[ip] = mute_output ? "" : result[:stdout] exit_statuses[ip] = result[:exit_status] end end rescue TimeoutError thread_map.values.each { |t| t.kill if t.alive? } raise "timeout reached waiting for hosts '#{ips.join(', ')}'" end if exit_statuses.values.all? { |x| success.any? { |c| x == c } } return({ :ssh_exit_statuses => exit_statuses, :ssh_output => output }) else # FIXME log return false end end |
#shutdown(args = {}) ⇒ Object
Deprovision: run the command. If no ips are provided from a previous provisioner, use the IPs gathered during startup.
454 455 456 457 458 459 460 461 462 |
# File 'lib/furnish/provisioners/ssh.rb', line 454 def shutdown(args={}) # XXX use the IPs we got during startup if we didn't get a new set. if args[:ips] and !args[:ips].empty? @ips = args[:ips] end return false if !ips or ips.empty? run_ssh_provision(shutdown_command) end |
#shutdown_command ⇒ Object
:attr: shutdown_command
The command to run on each remote host when this provisioner runs shutdown. Either #startup_command or #shutdown_command must be provided.
134 135 136 |
# File 'lib/furnish/provisioners/ssh.rb', line 134 furnish_property :shutdown_command, "The command to run on each remote host when this provisioner runs shutdown. Either startup_command or shutdown_command must be provided.", String |
#ssh(host, cmd) ⇒ Object
Performs the actual connection and execution.
313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 |
# File 'lib/furnish/provisioners/ssh.rb', line 313 def ssh(host, cmd) ret = { :exit_status => 0, :stdout => "", :stderr => "" } Net::SSH.start(host, username, ) do |ssh| ssh.open_channel do |ch| if stdin ch.send_data(stdin) ch.eof! end if require_pty ch.request_pty do |ch, success| unless success raise "The use_sudo setting requires a PTY, and your SSH is rejecting our attempt to get one." end end end ch.on_open_failed do |ch, code, desc| raise "Connection Error to #{username}@#{host}: #{desc}" end ch.exec(cmd) do |ch, success| unless success raise "Could not execute command '#{cmd}' on #{username}@#{host}" end if merge_output ch.on_data do |ch, data| log(host, data) ret[:stdout] << data end ch.on_extended_data do |ch, type, data| if type == 1 log(host, data) ret[:stdout] << data end end else ch.on_data do |ch, data| log(host, data) ret[:stdout] << data end ch.on_extended_data do |ch, type, data| ret[:stderr] << data if type == 1 end end ch.on_request("exit-status") do |ch, data| ret[:exit_status] = data.read_long end end end ssh.loop end return ret end |
#ssh_options ⇒ Object
Constructs the proper hash for Net::SSH.start options.
287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 |
# File 'lib/furnish/provisioners/ssh.rb', line 287 def opts = { :config => false, :keys_only => private_key_path || private_key } if password opts[:password] = password elsif private_key opts[:key_data] = [private_key] elsif private_key_path opts[:keys] = private_key_path end opts[:paranoid] = paranoid if opts[:paranoid] == :no_save_strict opts[:paranoid] = Net::SSH::Verifiers::NoSaveStrict.new end return opts end |
#startup(args = {}) ⇒ Object
Provision: run the command on all hosts and return the status from #run_ssh_provision. Will stuff the ips if passed regardless, so they can be used for #shutdown when nothing is expected to run in #startup.
445 446 447 448 |
# File 'lib/furnish/provisioners/ssh.rb', line 445 def startup(args={}) @ips = args[:ips] run_ssh_provision(startup_command) end |
#startup_command ⇒ Object
:attr: startup_command
The command to run on each remote host when this provisioner runs startup. Either #startup_command or #shutdown_command must be provided.
124 125 126 |
# File 'lib/furnish/provisioners/ssh.rb', line 124 furnish_property :startup_command, "The command to run on each remote host when this provisioner runs startup. Either startup_command or shutdown_command must be provided.", String |
#stdin ⇒ Object
:attr: stdin
If a string is provided, will be provided to the executing command as standard input. Cannot be used with #require_pty.
153 154 155 |
# File 'lib/furnish/provisioners/ssh.rb', line 153 furnish_property :stdin, "If a string is provided, will be provided to the executing command as standard input. Cannot be used with require_pty.", String |
#success ⇒ Object
:attr: success
If non-nil, exit statuses that are in the set will be considered successes. Default is to only treat 0 as a success.
201 202 203 |
# File 'lib/furnish/provisioners/ssh.rb', line 201 furnish_property :success, "If non-nil, exit statuses that are in the set will be considered successes.", Set |
#username ⇒ Object
:attr: username
Username which to SSH in as. Required, no default.
83 84 85 |
# File 'lib/furnish/provisioners/ssh.rb', line 83 furnish_property :username, "Username which to SSH in as. Required, no default.", String |