vagrant/plugins/pushes/ftp/push.rb
Chris Roberts ea25996b21
Update Vagrant behavior outside of installers
Remove customized require behaviors and modify the bin executable
to check for missing tools that Vagrant expects to exist when
running outside of an installer.
2025-04-02 11:40:17 -07:00

137 lines
4.2 KiB
Ruby

# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: BUSL-1.1
require "net/ftp"
require "pathname"
require_relative "adapter"
require_relative "errors"
module VagrantPlugins
module FTPPush
class Push < Vagrant.plugin("2", :push)
IGNORED_FILES = %w(. ..).freeze
DEFAULT_EXCLUDES = %w(.git .hg .svn .vagrant).freeze
def initialize(*)
super
@logger = Log4r::Logger.new("vagrant::pushes::ftp")
end
def push
# Grab files early so if there's an exception or issue, we don't have to
# wait and close the (S)FTP connection as well
files = nil
begin
files = Hash[*all_files.flat_map do |file|
relative_path = relative_path_for(file, base_dir)
destination = File.join(config.destination, relative_path)
file = File.expand_path(file, env.root_path)
[file, destination]
end]
rescue SystemStackError
raise Errors::TooManyFiles
end
ftp = "#{config.username}@#{config.host}:#{config.destination}"
env.ui.info "Uploading #{files.length} files to #{env.root_path} to #{ftp}"
connect do |ftp|
files.each do |local, remote|
env.ui.info "Uploading #{local} => #{remote}"
ftp.upload(local, remote)
end
end
end
# Helper method for creating the FTP or SFTP connection.
# @yield [Adapter]
def connect(&block)
klass = config.secure ? SFTPAdapter : FTPAdapter
ftp = klass.new(config.host, config.username, config.password,
passive: config.passive)
ftp.connect(&block)
end
# The list of all files that should be pushed by this push. This method
# only returns **files**, not folders or symlinks!
# @return [Array<String>]
def all_files
files = glob("#{base_dir}/**/*") + includes_files
filter_excludes!(files, config.excludes)
files.reject! { |f| !File.file?(f) }
files
end
# The list of files to include in addition to those specified in `dir`.
# @return [Array<String>]
def includes_files
includes = config.includes.flat_map do |i|
path = absolute_path_for(i, base_dir)
[path, "#{path}/**/*"]
end
glob("{#{includes.join(",")}}")
end
# Filter the excludes out of the given list. This method modifies the
# given list in memory!
#
# @param [Array<String>] list
# the filepaths
# @param [Array<String>] excludes
# the exclude patterns or files
def filter_excludes!(list, excludes)
excludes = Array(excludes)
excludes = excludes + DEFAULT_EXCLUDES
excludes = excludes.flat_map { |e| [e, "#{e}/*"] }
list.reject! do |file|
basename = relative_path_for(file, config.dir)
# Handle the special case where the file is outside of the working
# directory...
if basename.start_with?("../")
basename = file
end
excludes.any? { |e| File.fnmatch?(e, basename, File::FNM_DOTMATCH) }
end
end
# Get the list of files that match the given pattern.
# @return [Array<String>]
def glob(pattern)
Dir.glob(pattern, File::FNM_DOTMATCH).sort.reject do |file|
IGNORED_FILES.include?(File.basename(file))
end
end
# The absolute path to the given `path` and `parent`, unless the given
# path is absolute.
# @return [String]
def absolute_path_for(path, parent)
path = Pathname.new(path)
return path if path.absolute?
File.expand_path(path, parent)
end
# The relative path from the given `parent`. If files exist on another
# device, this will probably blow up.
# @return [String]
def relative_path_for(path, parent)
Pathname.new(path).relative_path_from(Pathname.new(parent)).to_s
rescue ArgumentError
return path
end
def base_dir
if Pathname.new(config.dir).absolute?
config.dir
else
File.join(File.expand_path(env.root_path), config.dir)
end
end
end
end
end