Add the FTP push
This commit is contained in:
parent
80851a887f
commit
8aaf5dc578
|
@ -0,0 +1,116 @@
|
|||
require "net/ftp"
|
||||
require "pathname"
|
||||
|
||||
require_relative "adapter"
|
||||
|
||||
module VagrantPlugins
|
||||
module FTPPush
|
||||
class Push < Vagrant.plugin("2", :push)
|
||||
IGNORED_FILES = %w(. ..).freeze
|
||||
|
||||
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 = Hash[*all_files.flat_map do |file|
|
||||
relative_path = relative_path_for(file, config.dir)
|
||||
destination = File.expand_path(File.join(config.destination, relative_path))
|
||||
[file, destination]
|
||||
end]
|
||||
|
||||
connect do |ftp|
|
||||
files.each do |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
|
||||
|
||||
# Parse the host into it's url and port parts.
|
||||
# @return [Array]
|
||||
def parse_host(host)
|
||||
if host.include?(":")
|
||||
host.split(":", 2)
|
||||
else
|
||||
[host, "22"]
|
||||
end
|
||||
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).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
|
|
@ -0,0 +1,313 @@
|
|||
require_relative "../../../base"
|
||||
require "fake_ftp"
|
||||
|
||||
require Vagrant.source_root.join("plugins/pushes/ftp/push")
|
||||
|
||||
describe VagrantPlugins::FTPPush::Push do
|
||||
include_context "unit"
|
||||
|
||||
let(:env) { isolated_environment }
|
||||
let(:config) do
|
||||
double("config",
|
||||
host: "127.0.0.1:21212",
|
||||
username: "sethvargo",
|
||||
password: "bacon",
|
||||
passive: false,
|
||||
secure: false,
|
||||
destination: "/var/www/site",
|
||||
)
|
||||
end
|
||||
|
||||
subject { described_class.new(env, config) }
|
||||
|
||||
describe "#push" do
|
||||
before(:all) do
|
||||
@server = FakeFtp::Server.new(21212, 21213)
|
||||
@server.start
|
||||
|
||||
@dir = Dir.mktmpdir
|
||||
|
||||
FileUtils.touch("#{@dir}/.hidden.rb")
|
||||
FileUtils.touch("#{@dir}/application.rb")
|
||||
FileUtils.touch("#{@dir}/config.rb")
|
||||
FileUtils.touch("#{@dir}/Gemfile")
|
||||
FileUtils.touch("#{@dir}/data.txt")
|
||||
FileUtils.mkdir("#{@dir}/empty_folder")
|
||||
end
|
||||
|
||||
after(:all) do
|
||||
FileUtils.rm_rf(@dir)
|
||||
@server.stop
|
||||
end
|
||||
|
||||
let(:server) { @server }
|
||||
|
||||
before do
|
||||
allow(config).to receive(:dir)
|
||||
.and_return(@dir)
|
||||
|
||||
allow(config).to receive(:includes)
|
||||
.and_return([])
|
||||
|
||||
allow(config).to receive(:excludes)
|
||||
.and_return(%w(*.rb))
|
||||
end
|
||||
|
||||
|
||||
it "pushes the files to the server" do
|
||||
subject.push
|
||||
expect(server.files).to eq(%w(Gemfile data.txt))
|
||||
end
|
||||
end
|
||||
|
||||
describe "#connect" do
|
||||
before do
|
||||
allow_any_instance_of(VagrantPlugins::FTPPush::FTPAdapter)
|
||||
.to receive(:connect)
|
||||
.and_yield(:ftp)
|
||||
allow_any_instance_of(VagrantPlugins::FTPPush::SFTPAdapter)
|
||||
.to receive(:connect)
|
||||
.and_yield(:sftp)
|
||||
end
|
||||
|
||||
context "when secure is requested" do
|
||||
before do
|
||||
allow(config).to receive(:secure)
|
||||
.and_return(true)
|
||||
end
|
||||
|
||||
it "yields a new SFTPAdapter" do
|
||||
expect { |b| subject.connect(&b) }.to yield_with_args(:sftp)
|
||||
end
|
||||
end
|
||||
|
||||
context "when secure is not requested" do
|
||||
before do
|
||||
allow(config).to receive(:secure)
|
||||
.and_return(false)
|
||||
end
|
||||
|
||||
it "yields a new FTPAdapter" do
|
||||
expect { |b| subject.connect(&b) }.to yield_with_args(:ftp)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#parse_host" do
|
||||
let(:result) { subject.parse_host(host) }
|
||||
|
||||
context "when no port is given" do
|
||||
let(:host) { "127.0.0.1" }
|
||||
|
||||
it "returns the url and port 22" do
|
||||
expect(result).to eq(["127.0.0.1", "22"])
|
||||
end
|
||||
end
|
||||
|
||||
context "when a port is given" do
|
||||
let(:host) { "127.0.0.1:23456" }
|
||||
|
||||
it "returns the url and port 23456" do
|
||||
expect(result).to eq(["127.0.0.1", "23456"])
|
||||
end
|
||||
end
|
||||
|
||||
context "when more than more port is given" do
|
||||
let(:host) { "127.0.0.1:22:33:44" }
|
||||
|
||||
it "returns the url and everything after" do
|
||||
expect(result).to eq(["127.0.0.1", "22:33:44"])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#all_files" do
|
||||
before(:all) do
|
||||
@dir = Dir.mktmpdir
|
||||
|
||||
FileUtils.touch("#{@dir}/.hidden.rb")
|
||||
FileUtils.touch("#{@dir}/application.rb")
|
||||
FileUtils.touch("#{@dir}/config.rb")
|
||||
FileUtils.touch("#{@dir}/Gemfile")
|
||||
FileUtils.mkdir("#{@dir}/empty_folder")
|
||||
FileUtils.mkdir("#{@dir}/folder")
|
||||
FileUtils.mkdir("#{@dir}/folder/.git")
|
||||
FileUtils.touch("#{@dir}/folder/.git/config")
|
||||
FileUtils.touch("#{@dir}/folder/server.rb")
|
||||
end
|
||||
|
||||
after(:all) do
|
||||
FileUtils.rm_rf(@dir)
|
||||
end
|
||||
|
||||
let(:files) do
|
||||
subject.all_files.map do |file|
|
||||
file.sub("#{@dir}/", "")
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
allow(config).to receive(:dir)
|
||||
.and_return(@dir)
|
||||
|
||||
allow(config).to receive(:includes)
|
||||
.and_return(%w(not_a_file.rb still_not_a_file.rb))
|
||||
|
||||
allow(config).to receive(:excludes)
|
||||
.and_return(%w(*.rb))
|
||||
end
|
||||
|
||||
it "returns the list of real files + includes, without excludes" do
|
||||
expect(files).to eq(%w(
|
||||
Gemfile
|
||||
folder/.git/config
|
||||
))
|
||||
end
|
||||
end
|
||||
|
||||
describe "includes_files" do
|
||||
before(:all) do
|
||||
@dir = Dir.mktmpdir
|
||||
|
||||
FileUtils.touch("#{@dir}/.hidden.rb")
|
||||
FileUtils.touch("#{@dir}/application.rb")
|
||||
FileUtils.touch("#{@dir}/config.rb")
|
||||
FileUtils.touch("#{@dir}/Gemfile")
|
||||
FileUtils.mkdir("#{@dir}/folder")
|
||||
FileUtils.mkdir("#{@dir}/folder/.git")
|
||||
FileUtils.touch("#{@dir}/folder/.git/config")
|
||||
FileUtils.touch("#{@dir}/folder/server.rb")
|
||||
end
|
||||
|
||||
after(:all) do
|
||||
FileUtils.rm_rf(@dir)
|
||||
end
|
||||
|
||||
let(:files) do
|
||||
subject.includes_files.map do |file|
|
||||
file.sub("#{@dir}/", "")
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
allow(config).to receive(:dir)
|
||||
.and_return(@dir)
|
||||
end
|
||||
|
||||
def set_includes(value)
|
||||
allow(config).to receive(:includes)
|
||||
.and_return(value)
|
||||
end
|
||||
|
||||
it "includes the file" do
|
||||
set_includes(["Gemfile"])
|
||||
expect(files).to eq(%w(
|
||||
Gemfile
|
||||
))
|
||||
end
|
||||
|
||||
it "includes the files that are subdirectories" do
|
||||
set_includes(["folder"])
|
||||
expect(files).to eq(%w(
|
||||
folder
|
||||
folder/.git
|
||||
folder/.git/config
|
||||
folder/server.rb
|
||||
))
|
||||
end
|
||||
|
||||
it "includes files that match a pattern" do
|
||||
set_includes(["*.rb"])
|
||||
expect(files).to eq(%w(
|
||||
.hidden.rb
|
||||
application.rb
|
||||
config.rb
|
||||
))
|
||||
end
|
||||
end
|
||||
|
||||
describe "#filter_excludes" do
|
||||
let(:dir) { "/root/dir" }
|
||||
|
||||
let(:list) do
|
||||
%W(
|
||||
#{dir}/.hidden.rb
|
||||
#{dir}/application.rb
|
||||
#{dir}/config.rb
|
||||
#{dir}/Gemfile
|
||||
#{dir}/folder
|
||||
#{dir}/folder/.git
|
||||
#{dir}/folder/.git/config
|
||||
#{dir}/folder/server.rb
|
||||
|
||||
/path/outside/you.rb
|
||||
/path/outside/me.rb
|
||||
/path/outside/folder/bacon.rb
|
||||
)
|
||||
end
|
||||
|
||||
before do
|
||||
allow(config).to receive(:dir)
|
||||
.and_return(dir)
|
||||
end
|
||||
|
||||
it "excludes files" do
|
||||
subject.filter_excludes!(list, %w(*.rb))
|
||||
|
||||
expect(list).to eq(%W(
|
||||
#{dir}/Gemfile
|
||||
#{dir}/folder
|
||||
#{dir}/folder/.git
|
||||
#{dir}/folder/.git/config
|
||||
))
|
||||
end
|
||||
|
||||
it "excludes files in a directory" do
|
||||
subject.filter_excludes!(list, %w(folder))
|
||||
|
||||
expect(list).to eq(%W(
|
||||
#{dir}/.hidden.rb
|
||||
#{dir}/application.rb
|
||||
#{dir}/config.rb
|
||||
#{dir}/Gemfile
|
||||
|
||||
/path/outside/you.rb
|
||||
/path/outside/me.rb
|
||||
/path/outside/folder/bacon.rb
|
||||
))
|
||||
end
|
||||
|
||||
it "excludes specific files in a directory" do
|
||||
subject.filter_excludes!(list, %w(/path/outside/folder/*.rb))
|
||||
|
||||
expect(list).to eq(%W(
|
||||
#{dir}/.hidden.rb
|
||||
#{dir}/application.rb
|
||||
#{dir}/config.rb
|
||||
#{dir}/Gemfile
|
||||
#{dir}/folder
|
||||
#{dir}/folder/.git
|
||||
#{dir}/folder/.git/config
|
||||
#{dir}/folder/server.rb
|
||||
|
||||
/path/outside/you.rb
|
||||
/path/outside/me.rb
|
||||
))
|
||||
end
|
||||
|
||||
it "excludes files outside the #dir" do
|
||||
subject.filter_excludes!(list, %w(/path/outside))
|
||||
|
||||
expect(list).to eq(%W(
|
||||
#{dir}/.hidden.rb
|
||||
#{dir}/application.rb
|
||||
#{dir}/config.rb
|
||||
#{dir}/Gemfile
|
||||
#{dir}/folder
|
||||
#{dir}/folder/.git
|
||||
#{dir}/folder/.git/config
|
||||
#{dir}/folder/server.rb
|
||||
))
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue