Advertisement
Guest User

Untitled

a guest
Feb 9th, 2017
102
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 3.48 KB | None | 0 0
  1. require 'pty'
  2. require 'expect'
  3. require 'semantic_logger'
  4.  
  5. # Sftp that works on both Ruby and JRuby.
  6. #
  7. # Example Usage:
  8. # Sftp.upload(
  9. # user: 'friend',
  10. # password: 'secure',
  11. # host: 'localhost',
  12. # input_file_name: 'sample.txt'
  13. # )
  14. #
  15. # PTY.spawn is used so that the password can be passed via stdin into the sftp command line process.
  16. #
  17. # The sftp ruby gem was extremely slow and could not upload files larger than 2GB.
  18. # This approach is much faster and does not suffer from the CPU and memory utilization issues
  19. # associated with the sftp ruby gem.
  20. #
  21. # Note: The retry mechanism is only required on JRuby.
  22. module Sftp
  23. include SemanticLogger::Loggable
  24.  
  25. class SftpError < RuntimeError
  26. end
  27.  
  28. class ConnectionFailure < SftpError
  29. end
  30.  
  31. class SpawnFailure < SftpError
  32. end
  33.  
  34. class ReadTimeout < SftpError
  35. end
  36.  
  37. class SendFailure < SftpError
  38. end
  39.  
  40. def self.upload(user:, password:, host:, port: 22, input_file_name:, output_file_name: input_file_name, path: nil, sftp_bin: 'sftp', connect_timeout_s: 30, read_timeout_s: 5)
  41. retry_count = 0
  42. PTY.spawn(sftp_bin, "-P#{port}", "#{user}@#{host}") do |reader, writer, pid|
  43. begin
  44. logger.trace "Pid: #{pid}"
  45. str = logger.measure_info("Connected using sftp process:[#{pid}] to #{host}:#{port}") do
  46. logger.trace 'Waiting for password'
  47. raise(SpawnFailure, "Failed to spawn #{sftp_bin}") unless reader.expect(/Password:.*/, read_timeout_s)
  48. writer.puts(password)
  49. logger.trace 'Waiting for Connected'
  50. raise(ConnectionFailure, 'Failed to connect') unless reader.expect(/Connected to #{host}.*/, connect_timeout_s)
  51. writer.puts "mkdir #{path}" if path
  52.  
  53. writer.puts 'progress'
  54. logger.trace 'Waiting for Progress disabled'
  55. raise(ReadTimeout, 'Failed to disable progress') unless reader.expect(/Progress meter disabled.*/, read_timeout_s)
  56. writer.puts "put #{input_file_name} #{output_file_name}"
  57. logger.trace 'Waiting for put echo'
  58. raise(ReadTimeout, 'Failed to put file') unless reader.expect(/put.*/, read_timeout_s)
  59. logger.trace 'Waiting for blank line'
  60. logger.trace reader.gets # Skip remainder of put line above
  61. while (str = reader.gets).nil?
  62. sleep 0.1
  63. # Add limit
  64. logger.trace 'Waiting for more blank lines'
  65. end
  66. str
  67. end
  68. if str && str.include?('Uploading')
  69. logger.measure_info("Uploaded #{input_file_name} as #{output_file_name}") do
  70. writer.puts 'progress'
  71. logger.trace 'Waiting for Progress enabled (download complete)'
  72. # No timeout on the time it takes to send the file
  73. reader.expect(/Progress meter enabled.*/)
  74. end
  75. else
  76. raise(SendFailure, "Failed to upload #{input_file_name} as #{output_file_name}: #{str}")
  77. end
  78. ensure
  79. logger.measure_info('Waiting for sftp process to stop', min_duration: 5) do
  80. logger.trace 'puts bye'
  81. writer.puts 'bye'
  82. logger.trace 'writer close'
  83. writer.close
  84. logger.trace 'reader close'
  85. reader.close
  86. logger.trace 'process wait'
  87. Process.wait(pid, Process::WNOHANG)
  88. logger.trace "Finished. Status: #{$?.inspect}"
  89. end
  90. end
  91. true
  92. end
  93. rescue SpawnFailure
  94. retry_count += 1
  95. if retry_count <= 5
  96. logger.info "Retry attempt: #{retry_count}"
  97. retry
  98. end
  99. end
  100.  
  101. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement