Last active
May 10, 2023 18:43
-
-
Save sephraim/da8973f8b31cbe0eb4c043103d68e08f to your computer and use it in GitHub Desktop.
[Ruby SFTP]
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# frozen_string_literal: true | |
require 'net/sftp' | |
module Integrations | |
module Google | |
# Upload feed file(s) & fileset descriptor(s) to the Google Partner SFTP dropbox | |
# | |
# @see Google Dropbox SFTP setup: https://support.google.com/youtube/answer/3071034 | |
# | |
# @example | |
# # Upload a single file & double-check it exists | |
# Integrations::Google::Dropbox.upload('path/to/feed.json') | |
# Integrations::Google::Dropbox.exist?('path/to/feed.json') | |
# | |
# # Upload multiple files & double-check they exist | |
# Integrations::Google::Dropbox.upload(['path/to/feed.filesetdesc.json', 'path/to/feed.json']) | |
# Integrations::Google::Dropbox.exist?(['path/to/feed.filesetdesc.json', 'path/to/feed.json']) | |
class Dropbox | |
# @type [String] Google Partner SFTP dropbox host | |
HOST = 'partnerupload.google.com' | |
private_constant :HOST | |
# @type [String] Google Partner SFTP dropbox port | |
PORT = '19321' | |
private_constant :PORT | |
# @type [String] Google Partner SFTP dropbox username | |
USERNAME = Rails.configuration.google_booking_sftp_username | |
private_constant :USERNAME | |
# @type [String] Google Partner SFTP dropbox id_rsa private key | |
PRIVATE_KEY = Rails.configuration.google_booking_sftp_private_key.gsub('\\n', "\n") | |
private_constant :PRIVATE_KEY | |
class << self | |
# Upload feed file(s) & fileset descriptor(s) to the Google Partner Generic SFTP dropbox | |
# | |
# @raise [Net::SFTP::StatusException] if the upload fails | |
# @raise [Net::SSH::AuthenticationFailed] if authentication fails | |
# @raise [ArgumentError] if local file is missing | |
# | |
# @param filepaths [String, Array<String>] feed filepath(s) | |
# @param debug [Boolean] whether to enable verbose logging; useful for troubleshooting connection issues | |
# @return [true] true if the upload was successful, else raise an exception | |
def upload(filepaths, debug: false) # rubocop:disable Metrics/MethodLength | |
filepaths = Array.wrap(filepaths) | |
Rails.logger.info( | |
"Uploading #{filepaths.count} file(s) to the Google Partner SFTP dropbox." | |
) | |
successful_uploads = 0 | |
sftp_connect(debug: debug) do |dropbox| | |
filepaths.each do |filepath| | |
dropbox.upload!(filepath) do |event, _uploader, *args| | |
upload_status(event: event, args: args) | |
successful_uploads += 1 if event == :close | |
end | |
end | |
end | |
Rails.logger.info( | |
"Successfully uploaded #{successful_uploads} file(s) to the Google Partner SFTP dropbox." | |
) | |
true | |
end | |
# Check if the given feed file(s) & fileset descriptor(s) have been uploaded to | |
# the Google Partner Generic SFTP dropbox | |
# | |
# @param filepaths [String, Array<String>] feed filepath(s) | |
# @param debug [Boolean] whether to enable verbose logging; useful for troubleshooting connection issues | |
# @return [Boolean] true if files exist in the dropbox, else false | |
def exist?(filepaths, debug: false) | |
filepaths = Array.wrap(filepaths) | |
files_to_check = filepaths.map { |f| File.basename(f) } | |
sftp_connect(debug: debug) do |dropbox| | |
existing_files = dropbox.dir.entries('.').map(&:name) | |
return (files_to_check - existing_files).empty? | |
end | |
end | |
private | |
# Establish an SFTP connection to the Google Partner Generic SFTP dropbox, perform the given block, | |
# close the connection, and delete the temporary private key file. | |
# | |
# @private | |
# | |
# @yield [dropbox] Gives the SFTP connection to the block | |
# @yieldparam dropbox [Net::SFTP] the SFTP connection | |
# @yieldreturn [void] | |
# | |
# @param debug [Boolean] whether to enable verbose logging; useful for troubleshooting connection issues | |
# @return [void] | |
def sftp_connect(debug: false) | |
verbose_level = debug ? :debug : false | |
Net::SFTP.start(HOST, USERNAME, port: PORT, keys: [tmp_private_key_file.path], auth_methods: ['publickey'], | |
non_interactive: true, verbose: verbose_level) do |dropbox| | |
yield dropbox if block_given? | |
end | |
ensure | |
tmp_private_key_file.close! # close/delete temp file after connection closed | |
@tmp_private_key_file = nil | |
end | |
# Log the status of the upload | |
# | |
# @private | |
# | |
# @param event [Symbol] the event that triggered the callback | |
# @param args [Array] the arguments passed to the callback; see Net::SFTP::Operations::Upload | |
# @return [void] | |
def upload_status(event:, args:) # rubocop:disable Metrics/MethodLength | |
status = case event | |
when :open | |
# args[0] : file metadata | |
"Starting upload: #{args[0].local} -> #{args[0].remote} (#{args[0].size} bytes}" | |
when :put | |
# args[0] : file metadata | |
# args[1] : byte offset in remote file | |
# args[2] : data being written (as string) | |
"Writing #{args[2].length} bytes to #{args[0].remote} starting at #{args[1]}" | |
when :close | |
# args[0] : file metadata | |
"Finished with #{args[0].remote}" | |
when :mkdir | |
# args[0] : remote path name | |
"Creating directory #{args[0]}" | |
end | |
Rails.logger.info status | |
end | |
# Create a temporary id_rsa private key file to use for SFTP connection | |
# | |
# @note Both the private key file and its associated instance variable must be manually deleted | |
# upon closing the SFTP connection. | |
# | |
# @private | |
# | |
# @return [Tempfile] | |
def tmp_private_key_file | |
@tmp_private_key_file ||= begin | |
file = Tempfile.new('id_rsa') | |
file.write(PRIVATE_KEY) | |
file.close | |
file | |
end | |
end | |
end | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment