Guest User

Untitled

a guest
Feb 19th, 2018
70
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 5.50 KB | None | 0 0
  1. require 'find'
  2. require 'rubygems'
  3. require 'digest/md5'
  4. gem 'net-sftp', '<2.0.0'
  5. require 'net/sftp'
  6.  
  7. class SftpDirPublisher
  8. CHECKSUM_FILENAME = ".checksums"
  9.  
  10. attr_reader :host, :username, :password, :remote_dir, :local_dir, :exclude
  11.  
  12. def initialize(host, username, password, remote_dir, local_dir, exclude=[])
  13. @host = host
  14. @username = username
  15. @password = password
  16. @remote_dir = remote_dir
  17. @local_dir = local_dir
  18. @verbose = true
  19. @exclude = exclude
  20. end
  21.  
  22. def upload(dry_run=false)
  23. create_checksums
  24. puts_if_verbose "Connecting to #{@host}..."
  25. Net::SFTP.start(@host, @username, @password, :timeout=>30) do |sftp|
  26.  
  27. puts_if_verbose "Fetching checksums..."
  28. local_checksums = YAML::load(File.open( local_path(CHECKSUM_FILENAME) ))
  29. remote_checksums = {}
  30. begin
  31. checksums_src = ''
  32. sftp.open_handle( server_path(CHECKSUM_FILENAME) ) do |handle|
  33. checksums_src = sftp.read(handle)
  34. end
  35. remote_checksums = YAML::load(checksums_src) unless checksums_src.nil? or checksums_src==''
  36. rescue Net::SFTP::Operations::StatusException=>se
  37. # server's checksum.yml is missing
  38. end
  39.  
  40. puts_if_verbose "Comparing checksum data..."
  41. to_upload, to_remove = hash_diff(local_checksums, remote_checksums)
  42.  
  43. if to_upload.length > 0 or to_remove.length > 0
  44. puts_if_verbose "Differences found:"
  45. to_upload.each {|f| puts_if_verbose " - (Upload) #{f}" }
  46. to_remove.each {|f| puts_if_verbose " - (Delete) #{f}"}
  47.  
  48. exit if dry_run
  49.  
  50. puts_if_verbose "Beginning sync..."
  51.  
  52. to_upload.each do |filename|
  53. begin
  54. puts_if_verbose " - #{remote_path(filename)}"
  55.  
  56. dir_name = File.dirname(filename)
  57. dir_segs = dir_name.split('/')
  58. #puts "Checking path: #{dir_segs.join( '/' )}"
  59. prog_path = []
  60. dir_segs.each do |partial_dir|
  61. begin
  62. prog_path << partial_dir
  63. sftp.mkdir( remote_path( prog_path ), :permissions=>0755 )
  64. puts_if_verbose " + #{remote_path( prog_path )}"
  65. rescue Net::SFTP::Operations::StatusException=>se
  66. # don't worry about it
  67. end
  68. end
  69.  
  70. sftp.put_file local_path(filename), remote_path(filename)
  71. sftp.open_handle( remote_path(filename) ) do |handle|
  72. sftp.fsetstat( handle, :permissions=>0644 )
  73. end
  74. rescue Net::SFTP::Operations::StatusException=>se
  75. puts_if_verbose " ! Error uploading '#{filename}': #{se}"
  76. puts_if_verbose; puts_if_verbose "Halted execution of upload."
  77. exit(1)
  78. end
  79. end
  80.  
  81. to_remove.each do |filename|
  82. begin
  83. sftp.remove remote_path(filename)
  84. puts_if_verbose " x #{remote_path(filename)}"
  85. rescue
  86. puts_if_verbose " ! Error removing '#{filename}': #{$!}"
  87. end
  88. end
  89.  
  90. begin
  91. sftp.put_file local_path(CHECKSUM_FILENAME), remote_path(CHECKSUM_FILENAME)
  92. sftp.open_handle( remote_path(CHECKSUM_FILENAME) ) do |handle|
  93. sftp.fsetstat( handle, :permissions=>0644 )
  94. end
  95. rescue
  96. puts_if_verbose " ! Error uploading '#{CHECKSUM_FILENAME}': #{$!}"
  97. end
  98.  
  99.  
  100. summary = "#{to_upload.length} file(s) uploaded"
  101. summary += ", #{to_remove.length} files(s) deleted" if to_remove.length > 0
  102.  
  103. puts summary
  104. else
  105. puts "No changes made. The server is up to date!"
  106. end
  107.  
  108. puts "Done."
  109. end
  110. end
  111.  
  112. protected
  113.  
  114. def create_checksums
  115. checksums = generate_from(@local_dir)
  116. File.open(local_path(CHECKSUM_FILENAME), 'w') do |f|
  117. f.write checksums.to_yaml
  118. end
  119. checksums
  120. end
  121.  
  122. # returns [files_to_upload, files_to_delete]
  123. def hash_diff(source={}, target={})
  124. # look for differences...
  125. src_files = source.fetch('files', {})
  126. tgt_files = target.fetch('files', {})
  127. to_update = []; to_delete = []; to_upload = []
  128.  
  129. tgt_files.each do |filename, checksum|
  130. if src_files.has_key? filename
  131. to_update << filename unless src_files[filename] == checksum
  132. else
  133. to_delete << filename
  134. end
  135. end
  136.  
  137. to_upload = src_files.keys - tgt_files.keys
  138.  
  139. # returns [files_to_upload, files_to_delete]
  140. [[to_upload, to_update].flatten.sort, to_delete.sort]
  141. end
  142.  
  143. # Create the checksums...
  144. def generate_from(cache_dir)
  145. checksums = { 'generated_on'=>Time.now, 'files'=>{} }
  146. checksum_path = local_path(CHECKSUM_FILENAME)
  147. puts "Generating checksums from #{cache_dir}"
  148. Find.find( cache_dir ) do |f|
  149. next if File.directory?( f ) or f == checksum_path or has_exclusion(f,cache_dir)
  150.  
  151. checksums['files'][f.gsub("#{cache_dir}/", '')] = ::Digest::MD5.hexdigest( File.read(f) )
  152. end
  153. checksums
  154. end
  155.  
  156. def has_exclusion(path, cache_dir)
  157. path = path.gsub("#{cache_dir}/", '')
  158. exclude.any? do |test_pattern|
  159. case test_pattern
  160. when String: path.start_with?(test_pattern)
  161. when Regexp: path =~ test_pattern
  162. end
  163. end
  164. end
  165.  
  166. def server_path(path)
  167. [@remote_dir, path].flatten.join('/')
  168. end
  169. alias_method :remote_path, :server_path
  170.  
  171. def local_path(path)
  172. File.join(@local_dir, path)
  173. end
  174.  
  175. def puts_if_verbose(msg='')
  176. puts(msg) if @verbose
  177. end
  178.  
  179. end
  180.  
  181. class String
  182. def starts_with?(prefix)
  183. prefix = prefix.to_s
  184. self[0, prefix.length] == prefix
  185. end
  186. end
Add Comment
Please, Sign In to add comment