vagrant/plugins/commands/upload/command.rb

237 lines
7.8 KiB
Ruby

require 'optparse'
require "rubygems/package"
module VagrantPlugins
module CommandUpload
class Command < Vagrant.plugin("2", :command)
VALID_COMPRESS_TYPES = [:tgz, :zip].freeze
def self.synopsis
"upload to machine via communicator"
end
def execute
options = {}
opts = OptionParser.new do |o|
o.banner = "Usage: vagrant upload [options] <source> [destination] [name|id]"
o.separator ""
o.separator "Options:"
o.separator ""
o.on("-t", "--temporary", "Upload source to temporary directory") do |t|
options[:temporary] = t
end
o.on("-c", "--compress", "Use gzip compression for upload") do |c|
options[:compress] = c
end
o.on("-C", "--compression-type=TYPE", "Type of compression to use (#{VALID_COMPRESS_TYPES.join(", ")})") do |c|
options[:compression_type] = c.to_sym
options[:compress] = true
end
end
argv = parse_options(opts)
return if !argv
case argv.size
when 3
source, destination, guest = argv
when 2, 1
source = argv[0]
if @env.active_machines.map(&:first).map(&:to_s).include?(argv[1])
guest = argv[1]
else
destination = argv[1]
end
else
raise Vagrant::Errors::CLIInvalidUsage, help: opts.help.chomp
end
# NOTE: We do this to handle paths on Windows like: "..\space dir\"
# because the final separater acts to escape the quote and ends up
# in the source value.
source = source.sub(/["']$/, "")
destination ||= File.basename(source)
if File.file?(source)
type = :file
elsif File.directory?(source)
type = :directory
else
raise Vagrant::Errors::UploadSourceMissing,
source: source
end
with_target_vms(guest, single_target: true) do |machine|
if options[:temporary]
if !machine.guest.capability?(:create_tmp_path)
raise Vagrant::Errors::UploadMissingTempCapability
end
extension = File.extname(source) if type == :file
destination = machine.guest.capability(:create_tmp_path, type: type, extension: extension)
end
if options[:compress]
compression_setup!(machine, options)
@env.ui.info(I18n.t("vagrant.commands.upload.compress",
source: source,
type: options[:compression_type]
))
destination_decompressed = destination
destination = machine.guest.capability(:create_tmp_path, type: :file, extension: ".#{options[:compression_type]}")
source_display = source
source = options[:compression_type] == :zip ? compress_source_zip(source) : compress_source_tgz(source)
end
@env.ui.info(I18n.t("vagrant.commands.upload.start",
source: source,
destination: destination
))
# If the source is a directory, attach a `/.` to the end so we
# upload the contents to the destination instead of within a
# folder at the destination
if File.directory?(source) && !source.end_with?(".")
upload_source = File.join(source, ".")
else
upload_source = source
end
machine.communicate.upload(upload_source, destination)
if options[:compress]
@env.ui.info(I18n.t("vagrant.commands.upload.decompress",
destination: destination_decompressed,
type: options[:compression_type]
))
machine.guest.capability(options[:decompression_method], destination, destination_decompressed, type: type)
destination = destination_decompressed
FileUtils.rm(source)
source = source_display
end
end
@env.ui.info(I18n.t("vagrant.commands.upload.complete",
source: source,
destination: destination
))
# Success, exit status 0
0
end
# Setup compression options and validate host and guest have capability
# to handle compression
#
# @param [Vagrant::Machine] machine Vagrant guest machine
# @param [Hash] options Command options
def compression_setup!(machine, options)
if !options[:compression_type]
if machine.guest.capability_host_chain.first[0] == :windows
options[:compression_type] = :zip
else
options[:compression_type] = :tgz
end
end
if !VALID_COMPRESS_TYPES.include?(options[:compression_type])
raise Vagrant::Errors::UploadInvalidCompressionType,
type: options[:compression_type],
valid_types: VALID_COMPRESS_TYPES.join(", ")
end
options[:decompression_method] = "decompress_#{options[:compression_type]}".to_sym
if !machine.guest.capability?(options[:decompression_method])
raise Vagrant::Errors::UploadMissingExtractCapability,
type: options[:compression_type]
end
end
# Compress path using zip into temporary file
#
# @param [String] path Path to compress
# @return [String] path to compressed file
def compress_source_zip(path)
require "zip"
zipfile = Tempfile.create(["vagrant", ".zip"])
zipfile.close
if File.file?(path)
source_items = [path]
else
source_items = Dir.glob(File.join(path, "**", "**", "*"))
end
c_dir = nil
Zip::File.open(zipfile.path, Zip::File::CREATE) do |zip|
source_items.each do |source_item|
next if File.directory?(source_item)
trim_item = source_item.sub(path, "").sub(%r{^[/\\]}, "")
dirname = File.dirname(trim_item)
zip.mkdir dirname if c_dir != dirname
c_dir = dirname
zip.get_output_stream(trim_item) do |f|
source_file = File.open(source_item, "rb")
while data = source_file.read(2048)
f.write(data)
end
end
end
end
zipfile.path
end
# Compress path using tar and gzip into temporary file
#
# @param [String] path Path to compress
# @return [String] path to compressed file
def compress_source_tgz(path)
tarfile = Tempfile.create(["vagrant", ".tar"])
tarfile.close
tarfile = File.open(tarfile.path, "wb+")
tgzfile = Tempfile.create(["vagrant", ".tgz"])
tgzfile.close
tgzfile = File.open(tgzfile.path, "wb")
tar = Gem::Package::TarWriter.new(tarfile)
tgz = Zlib::GzipWriter.new(tgzfile)
if File.file?(path)
tar.add_file(File.basename(path), File.stat(path).mode) do |io|
File.open(path, "rb") do |file|
while bytes = file.read(4096)
io.write(bytes)
end
end
end
else
Dir.glob(File.join(path, "**/**/*")).each do |item|
rel_path = item.sub(path, "")
item_mode = File.stat(item).mode
if File.directory?(item)
tar.mkdir(rel_path, item_mode)
else
tar.add_file(rel_path, item_mode) do |io|
File.open(item, "rb") do |file|
while bytes = file.read(4096)
io.write(bytes)
end
end
end
end
end
end
tar.close
tarfile.rewind
while bytes = tarfile.read(4096)
tgz.write bytes
end
tgz.close
tgzfile.close
tarfile.close
File.delete(tarfile.path)
tgzfile.path
end
end
end
end