vaguerent/lib/vagrant/util/uploader.rb

110 lines
3.3 KiB
Ruby

# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: BUSL-1.1
require "uri"
require "log4r"
require "vagrant/util/busy"
require "vagrant/util/platform"
require "vagrant/util/subprocess"
require "vagrant/util/curl_helper"
module Vagrant
module Util
# This class uploads files using various protocols by subprocessing
# to cURL. cURL is a much more capable and complete download tool than
# a hand-rolled Ruby library, so we defer to its expertise.
class Uploader
# @param [String] destination Valid URL to upload file to
# @param [String] file Location of file to upload on disk
# @param [Hash] options
# @option options [Vagrant::UI] :ui UI interface for output
# @option options [String, Symbol] :method Request method for upload
def initialize(destination, file, options=nil)
options ||= {}
@logger = Log4r::Logger.new("vagrant::util::uploader")
@destination = destination.to_s
@file = file.to_s
@ui = options[:ui]
@request_method = options[:method]
if !@request_method
@request_method = "PUT"
end
@request_method = @request_method.to_s.upcase
end
def upload!
data_proc = Vagrant::Util::CurlHelper.capture_output_proc(@logger, @ui)
@logger.info("Uploader starting upload: ")
@logger.info(" -- Source: #{@file}")
@logger.info(" -- Destination: #{@destination}")
options = build_options
subprocess_options = {notify: :stderr}
begin
execute_curl(options, subprocess_options, &data_proc)
rescue Errors::UploaderError => e
raise
ensure
@ui.clear_line if @ui
end
end
protected
def build_options
options = [@destination, "--request", @request_method, "--upload-file", @file, "--fail"]
return options
end
def execute_curl(options, subprocess_options, &data_proc)
options = options.dup
options << subprocess_options
# Create the callback that is called if we are interrupted
interrupted = false
int_callback = Proc.new do
@logger.info("Uploader interrupted!")
interrupted = true
end
# Execute!
result = Busy.busy(int_callback) do
Subprocess.execute("curl", *options, &data_proc)
end
# If the upload was interrupted, then raise a specific error
raise Errors::UploaderInterrupted if interrupted
# If it didn't exit successfully, we need to parse the data and
# show an error message.
if result.exit_code != 0
@logger.warn("Uploader exit code: #{result.exit_code}")
check = result.stderr.match(/\n*curl:\s+\((?<code>\d+)\)\s*(?<error>.*)$/)
if !check
err_msg = result.stderr
else
err_msg = check[:error]
end
raise Errors::UploaderError,
exit_code: result.exit_code,
message: err_msg
end
if @ui
@ui.clear_line
# Windows doesn't clear properly for some reason, so we just
# output one more newline.
@ui.detail("") if Platform.windows?
end
result
end
end
end
end