Module: RHC::SSHHelpers

Defined Under Namespace

Classes: MultipleGearTask

Instance Method Summary collapse

Instance Method Details

#check_ssh_executable!(path) ⇒ Object

return supplied ssh executable, if valid (executable, searches $PATH). if none was supplied, return installed ssh, if any.



446
447
448
449
450
451
452
453
454
455
456
# File 'lib/rhc/ssh_helpers.rb', line 446

def check_ssh_executable!(path)
  if not path
    raise RHC::InvalidSSHExecutableException.new("No system SSH available. Please use the --ssh option to specify the path to your SSH executable, or install SSH.") unless has_ssh?
    'ssh'
  else
    bin_path = path.split(' ').first
    raise RHC::InvalidSSHExecutableException.new("SSH executable '#{bin_path}' does not exist.") unless File.exist?(bin_path) or exe?(bin_path)
    raise RHC::InvalidSSHExecutableException.new("SSH executable '#{bin_path}' is not executable.") unless File.executable?(bin_path) or exe?(bin_path)
    path
  end
end

#exe?(executable) ⇒ Boolean

Returns:

  • (Boolean)


376
377
378
379
380
# File 'lib/rhc/ssh_helpers.rb', line 376

def exe?(executable)
  ENV['PATH'].split(File::PATH_SEPARATOR).any? do |directory|
    File.executable?(File.join(directory, executable.to_s))
  end
end

#fingerprint_for_default_keyObject



407
408
409
# File 'lib/rhc/ssh_helpers.rb', line 407

def fingerprint_for_default_key
  fingerprint_for_local_key(RHC::Config.ssh_pub_key_file_path)
end

#fingerprint_for_local_key(key) ⇒ Object



394
395
396
397
398
399
400
401
402
403
404
405
# File 'lib/rhc/ssh_helpers.rb', line 394

def fingerprint_for_local_key(key)
  Net::SSH::KeyFactory.load_public_key(key).fingerprint
rescue NoMethodError, NotImplementedError => e
  ssh_keygen_fallback key
  nil
rescue OpenSSL::PKey::PKeyError, Net::SSH::Exception => e
  error e.message
  nil
rescue => e
  debug e.message
  nil
end

#generate_ssh_key_ruby(type = "RSA", bits = 2048, comment = "OpenShift-Key") ⇒ Object

Public: Generate an SSH key and store it in ~/.ssh/id_rsa

type - The String type RSA or DSS. bits - The Integer value for number of bits. comment - The String comment for the key

Examples

generate_ssh_key_ruby
# => /home/user/.ssh/id_rsa.pub

Returns nil on failure or public key location as a String on success



351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
# File 'lib/rhc/ssh_helpers.rb', line 351

def generate_ssh_key_ruby(type="RSA", bits = 2048, comment = "OpenShift-Key")
  key = RHC::Vendor::SSHKey.generate(:type => type,
                                     :bits => bits,
                                     :comment => comment)
  ssh_dir = RHC::Config.ssh_dir
  priv_key = RHC::Config.ssh_priv_key_file_path
  pub_key = RHC::Config.ssh_pub_key_file_path

  if File.exists?(priv_key)
    say "SSH key already exists: #{priv_key}.  Reusing..."
    return nil
  else
    unless File.exists?(ssh_dir)
      FileUtils.mkdir_p(ssh_dir)
      File.chmod(0700, ssh_dir)
    end
    File.open(priv_key, 'w') {|f| f.write(key.private_key)}
    File.chmod(0600, priv_key)
    File.open(pub_key, 'w') {|f| f.write(key.ssh_public_key)}

    ssh_add
  end
  pub_key
end

#has_ssh?Boolean

return whether or not SSH is installed

Returns:

  • (Boolean)


434
435
436
437
438
439
440
441
442
# File 'lib/rhc/ssh_helpers.rb', line 434

def has_ssh?
  @has_ssh ||= begin
    @ssh_version = nil
    ssh_version
    $?.success?
  rescue
    false
  end
end

#restore_snapshot(app, filename, ssh_executable = nil) ⇒ Object



290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
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
# File 'lib/rhc/ssh_helpers.rb', line 290

def restore_snapshot(app, filename, ssh_executable=nil)
  include_git = RHC::Helpers.windows? ? true : RHC::TarGz.contains(filename, './*/git')
  ssh_uri = URI.parse(app.ssh_url)
  ssh_executable = check_ssh_executable! options.ssh

  ssh_cmd = "cat '#{filename}' | #{ssh_executable} #{ssh_uri.user}@#{ssh_uri.host} 'restore#{include_git ? ' INCLUDE_GIT' : ''}'"
  ssh_stderr = " 2>/dev/null"
  debug ssh_cmd

  say "Restoring from snapshot #{filename} to application '#{app.name}' ... "

  begin
    if !RHC::Helpers.windows?
      status, output = exec(ssh_cmd + (debug? ? '' : ssh_stderr))
      if status != 0
        debug output
        raise RHC::SnapshotRestoreException.new "Error in trying to restore snapshot. You can try to restore manually by running:\n#{ssh_cmd}"
      end
    else
      ssh = Net::SSH.start(ssh_uri.host, ssh_uri.user)
      ssh.open_channel do |channel|
        channel.exec("restore#{include_git ? ' INCLUDE_GIT' : ''}") do |ch, success|
          channel.on_data do |ch, data|
            debug data
          end
          channel.on_extended_data do |ch, type, data|
            debug data
          end
          channel.on_close do |ch|
            debug "Terminating..."
          end
          File.open(filename, 'rb') do |file|
            file.chunk(4096) do |chunk|
              channel.send_data chunk
            end
          end
          channel.eof!
        end
      end
      ssh.loop
    end
  rescue Timeout::Error, Errno::EADDRNOTAVAIL, Errno::EADDRINUSE, Errno::EHOSTUNREACH, Errno::ECONNREFUSED, Net::SSH::AuthenticationFailed => e
    debug e.backtrace
    raise RHC::SnapshotRestoreException.new "Error in trying to restore snapshot. You can try to restore manually by running:\n#{ssh_cmd}"
  end

  success 'done'
end

#run_on_gears(command, gears, opts = {}, &block) ⇒ Object



135
136
137
138
# File 'lib/rhc/ssh_helpers.rb', line 135

def run_on_gears(command, gears, opts={}, &block)
  debug "Executing #{command} on each of #{gears.inspect}"
  MultipleGearTask.new(command, gears, {:limit => options.limit, :always_prefix => options.always_prefix, :raw => options.raw}.merge(opts)).run(&block)
end

#save_snapshot(app, filename, for_deployment = false, ssh_executable = nil) ⇒ Object



251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
# File 'lib/rhc/ssh_helpers.rb', line 251

def save_snapshot(app, filename, for_deployment=false, ssh_executable=nil)
  ssh_uri = URI.parse(app.ssh_url)
  ssh_executable = check_ssh_executable! ssh_executable

  snapshot_cmd = for_deployment ? 'gear archive-deployment' : 'snapshot'
  ssh_cmd = "#{ssh_executable} #{ssh_uri.user}@#{ssh_uri.host} '#{snapshot_cmd}' > #{filename}"
  ssh_stderr = " 2>/dev/null"
  debug ssh_cmd

  say "Pulling down a snapshot of application '#{app.name}' to #{filename} ... "

  begin
    if !RHC::Helpers.windows?
        status, output = exec(ssh_cmd + (debug? ? '' : ssh_stderr))
        if status != 0
          debug output
          raise RHC::SnapshotSaveException.new "Error in trying to save snapshot. You can try to save manually by running:\n#{ssh_cmd}"
        end
    else
      Net::SSH.start(ssh_uri.host, ssh_uri.user) do |ssh|
        File.open(filename, 'wb') do |file|
          ssh.exec! "snapshot" do |channel, stream, data|
            if stream == :stdout
              file.write(data)
            else
              debug data
            end
          end
        end
      end
    end
  rescue Timeout::Error, Errno::EADDRNOTAVAIL, Errno::EADDRINUSE, Errno::EHOSTUNREACH, Errno::ECONNREFUSED, Net::SSH::AuthenticationFailed => e
    debug e.backtrace
    raise RHC::SnapshotSaveException.new "Error in trying to save snapshot. You can try to save manually by running:\n#{ssh_cmd}"
  end

  success 'done'
end

#ssh_command_for_op(operation) ⇒ Object



146
147
148
149
150
# File 'lib/rhc/ssh_helpers.rb', line 146

def ssh_command_for_op(operation)
  #case operation
  raise RHC::OperationNotSupportedException, "The operation #{operation} is not supported."
  #end
end

#ssh_key_triple_for(key) ⇒ Object

for an SSH public key specified by ‘key’, return a triple

type, content, comment

which is basically the space-separated list of the SSH public key content



414
415
416
417
418
419
420
421
422
# File 'lib/rhc/ssh_helpers.rb', line 414

def ssh_key_triple_for(key)
  begin
    IO.read(key).chomp.split
  rescue Errno::ENOENT => e
    raise ::RHC::KeyFileNotExistentException.new("File '#{key}' does not exist.")
  rescue Errno::EACCES => e
    raise ::RHC::KeyFileAccessDeniedException.new("Access denied to '#{key}'.")
  end
end

#ssh_key_triple_for_default_keyObject



424
425
426
# File 'lib/rhc/ssh_helpers.rb', line 424

def ssh_key_triple_for_default_key
  ssh_key_triple_for(RHC::Config.ssh_pub_key_file_path)
end

#ssh_keygen_fallback(path) ⇒ Object

For Net::SSH versions (< 2.0.11) that does not have Net::SSH::KeyFactory.load_public_key, we drop to shell to get the key’s fingerprint



385
386
387
388
389
390
391
392
# File 'lib/rhc/ssh_helpers.rb', line 385

def ssh_keygen_fallback(path)
  fingerprint = `ssh-keygen -lf #{path} 2>&1`.split(' ')[1]

  if $?.exitstatus != 0
    error "Unable to compute SSH public key finger print for #{path}"
  end
  fingerprint
end

#ssh_ruby(host, username, command, compression = false, request_pty = false, &block) ⇒ Object

Public: Run ssh command on remote host

host - The String of the remote hostname to ssh to. username - The String username of the remote user to ssh as. command - The String command to run on the remote host. compression - Use compression in ssh, set to false if sending files. request_pty - Request for pty, set to false when pipe a file. block - Will yield this block and send the channel if provided.

Examples

ssh_ruby('myapp-t.rhcloud.com',
          '109745632b514e9590aa802ec015b074',
          'rhcsh tail -f $OPENSHIFT_LOG_DIR/*"')
# => true

Returns true on success



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/rhc/ssh_helpers.rb', line 169

def ssh_ruby(host, username, command, compression=false, request_pty=false, &block)
  debug "Opening Net::SSH connection to #{host}, #{username}, #{command}"
  exit_status = 0
  options = {:compression => compression}
  options[:verbose] = :debug if debug?
  Net::SSH.start(host, username, options) do |session|
    #:nocov:
    channel = session.open_channel do |channel|
      if request_pty
        channel.request_pty do |ch, success|
          say "pty could not be obtained" unless success
        end
      end
      channel.exec(command) do |ch, success|
        channel.on_data do |ch, data|
          print data
        end
        channel.on_extended_data do |ch, type, data|
          print data
        end
        channel.on_close do |ch|
          debug "Terminating ... "
        end
        channel.on_request("exit-status") do |ch, data|
          exit_status = data.read_long
        end
        yield channel if block_given?
        channel.eof!
      end
    end
    session.loop
    #:nocov:
  end
  raise RHC::SSHCommandFailed.new(exit_status) if exit_status != 0
rescue Errno::ECONNREFUSED => e
  debug_error e
  raise RHC::SSHConnectionRefused.new(host, username)
rescue Net::SSH::AuthenticationFailed => e
  debug_error e
  raise RHC::SSHAuthenticationFailed.new(host, username)
rescue SocketError => e
  debug_error e
  raise RHC::ConnectionFailed, "The connection to #{host} failed: #{e.message}"
end

#ssh_send_file_ruby(host, username, command, filename) ⇒ Object

Public: Run ssh command on remote host and pipe the specified file contents to the command input

host - The String of the remote hostname to ssh to. username - The String username of the remote user to ssh as. command - The String command to run on the remote host. filename - The String path to file to send.



223
224
225
226
227
228
229
230
231
232
# File 'lib/rhc/ssh_helpers.rb', line 223

def ssh_send_file_ruby(host, username, command, filename)
  filename = File.expand_path(filename)
  ssh_ruby(host, username, command) do |channel|
    File.open(filename, 'rb') do |file|
      file.chunk(1024) do |chunk|
        channel.send_data chunk
      end
    end
  end
end

#ssh_send_url_ruby(host, username, command, content_url) ⇒ Object

Public: Run ssh command on remote host and pipe the specified url contents to the command input

host - The String of the remote hostname to ssh to. username - The String username of the remote user to ssh as. command - The String command to run on the remote host. content_url - The url with the content to pipe to command.



242
243
244
245
246
247
248
249
# File 'lib/rhc/ssh_helpers.rb', line 242

def ssh_send_url_ruby(host, username, command, content_url)
  content_url = URI.parse(URI.encode(content_url.to_s))
  ssh_ruby(host, username, command) do |channel|
    HTTPClient.new.get_content(content_url) do |chunk|
      channel.send_data chunk
    end
  end
end

#ssh_versionObject

check the version of SSH that is installed



429
430
431
# File 'lib/rhc/ssh_helpers.rb', line 429

def ssh_version
  @ssh_version ||= `ssh -V 2>&1`.strip
end

#table_from_gears(command, groups, opts = {}, &block) ⇒ Object



140
141
142
143
144
# File 'lib/rhc/ssh_helpers.rb', line 140

def table_from_gears(command, groups, opts={}, &block)
  cells = run_on_gears(command, groups, {:as => :table}.merge(opts), &block)
  cells.each{ |r| r.concat(r.pop.first.split(opts[:split_cells_on])) } if !block_given? && opts[:split_cells_on]
  say table cells, opts unless options.raw
end