127 lines
3.9 KiB
Ruby
127 lines
3.9 KiB
Ruby
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, config.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 #{env.root_path} to #{ftp}"
|
|
|
|
connect do |ftp|
|
|
files.each do |local, remote|
|
|
@logger.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("#{config.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, config.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
|
|
end
|
|
end
|
|
end
|