module Eco module API module Common module Session class SFTP def initialize (enviro:) raise "Required Environment object (enviro:). Given: #{enviro}" if enviro && !enviro.is_a?(Eco::API::Common::Session::Environment) @enviro = enviro end def host @host ||= fetch_host end # @see Net::SFTP::Session def sftp_session require "net/sftp" begin @sftp_session ||= Net::SFTP.start( host, fetch_user, **session_options ) rescue Exception => e msg = "Could not open SFTP session. Possible misconfiguration: #{e}" logger.error(msg) raise msg end @sftp_session end # @see Net::SFTP::Operations::Dir#entries def entries(path) sftp_session.dir.entries(path).sort_by {|rf| rf.name} end # **Files** of the remote directory. # @see Net::SFTP::Operations::Dir#entries # @param path [String] remote directory `path` # @param pattern [Regexp] if given, filters by using this pattern # @return [Array] def files(path, pattern: nil) entries = entries(path).select {|remote_file| remote_file.file?} return entries unless pattern entries.select {|remote_file| remote_file.name =~ pattern} end # **Folders** of the remote directory. # @see Net::SFTP::Operations::Dir#entries # @param path [String] remote directory `path` # @param pattern [Regexp] if given, filters by using this pattern # @return [Array] def folders(path, pattern: nil) entries = entries(path).select {|remote_file| remote_file.directory?} return entries unless pattern entries.select {|remote_file| remote_file.name =~ pattern} end # @see Net::SFTP::Session#rename def move(fullname_source, fullname_dest, flags=0x0001, override: true, &callback) begin sftp_session.rename!(fullname_source, fullname_dest, flags, &callback) rescue Net::SFTP::StatusException => e raise unless override sftp_session.remove(fullname_dest) sftp_session.rename!(fullname_source, fullname_dest, flags, &callback) end end # Downloads the files specified to a local folder # @see Net::SFTP::Operations::Download # @param files [String, Array] full path to remote file(s) to be downloaded # @param local_folder [String] local destination folder (`"."` if not specified) def download(files, local_folder: nil) puts "Creating local files:" [files].flatten.compact.map do |fullname| basename = windows_basename(fullname) dest_fullname = File.join(local_folder || ".", basename) puts " • #{dest_fullname}" sftp_session.download(fullname, dest_fullname) end.each do |dw| # run SSH event loop while dw.active? dw.wait end end private def session_options { non_interactive: true }.tap do |opts| if password? opts.merge!({password: fetch_password}) else opts.merge!({ keys: fetch_key_files, keys_only: true }) end end end def windows_basename(remote_fullname) parts = remote_fullname.split(/[\\\/]/).map {|node| node.gsub(/[<>:\\\/\|?*]/, '_')} local_fullname = File.join(*parts) File.basename(local_fullname) end def logger @enviro&.logger || ::Logger.new(IO::NULL) end def config @enviro.config || {} end def fetch_host config.sftp.host || ENV['SFTP_HOST'] end def fetch_user config.sftp.user || ENV['SFTP_USERNAME'] end def password? !!fetch_password end def fetch_password config.sftp.password || ENV['SFTP_PASSWORD'] end def fetch_key_files [config.sftp.key_file || ENV['SFTP_KEY_FILE']] end def fetch_base_path config.sftp.base_path || ENV['SFTP_BASE_PATH'] end end end end end end