-
Notifications
You must be signed in to change notification settings - Fork 4.4k
/
push.rb
137 lines (118 loc) · 4.18 KB
/
push.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# 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