Merge pull request #8939 from briancain/8933/master/source-path-file-prov

Align file provisioner functionality on all platforms
This commit is contained in:
Brian Cain 2017-09-05 15:54:52 -07:00 committed by GitHub
commit 089117bc0f
5 changed files with 165 additions and 7 deletions

View File

@ -83,9 +83,22 @@ module VagrantPlugins
raise_winrm_exception(e, "run_wql", query)
end
# @param from [Array<String>, String] a single path or folder, or an
# array of paths and folders to upload to the guest
# @param to [String] a path or folder on the guest to upload to
# @return [FixNum] Total size transfered from host to guest
def upload(from, to)
file_manager = WinRM::FS::FileManager.new(connection)
file_manager.upload(from, to)
if from.is_a?(Array)
# Preserve return FixNum of bytes transfered
return_bytes = 0
from.each do |file|
return_bytes += file_manager.upload(file, to)
end
return return_bytes
else
file_manager.upload(from, to)
end
end
def download(from, to)

View File

@ -11,15 +11,28 @@ module VagrantPlugins
if File.directory?(source)
# We need to make sure the actual destination folder
# also exists before uploading, otherwise
# you will get nested folders. We also need to append
# a './' to the source folder so we copy the contents
# rather than the folder itself, in case a users destination
# folder differs from its source.
# you will get nested folders
#
# https://serverfault.com/questions/538368/make-scp-always-overwrite-or-create-directory
# https://unix.stackexchange.com/questions/292641/get-scp-path-behave-like-rsync-path/292732
command = "mkdir -p \"%s\"" % destination
source << "/."
if !destination.end_with?(File::SEPARATOR) &&
!source.end_with?("#{File::SEPARATOR}.")
# We also need to append a '/.' to the source folder so we copy
# the contents rather than the folder itself, in case a users
# destination folder differs from its source
#
# If source is set as `source/` it will lose the trailing
# slash due to how `File.expand_path` works, so we don't need
# a conditional for that case.
if @machine.config.vm.communicator == :winrm
# windows needs an array of paths because of the
# winrm-fs function Vagrant is using to upload file/folder.
source = Dir["#{source}#{File::SEPARATOR}*"]
else
source << "#{File::SEPARATOR}."
end
end
else
command = "mkdir -p \"%s\"" % File.dirname(destination)
end

View File

@ -31,6 +31,35 @@ describe VagrantPlugins::CommunicatorWinRM::WinRMShell do
end
end
describe "#upload" do
let(:fm) { double("file_manager") }
it "should call file_manager.upload for each passed in path" do
from = ["/path", "/path/folder", "/path/folder/file.py"]
to = "/destination"
size = 80
allow(WinRM::FS::FileManager).to receive(:new).with(connection)
.and_return(fm)
allow(fm).to receive(:upload).and_return(size)
expect(fm).to receive(:upload).exactly(from.size).times
expect(subject.upload(from, to)).to eq(size*from.size)
end
it "should call file_manager.upload once for a single path" do
from = "/path/folder/file.py"
to = "/destination"
size = 80
allow(WinRM::FS::FileManager).to receive(:new).with(connection)
.and_return(fm)
allow(fm).to receive(:upload).and_return(size)
expect(fm).to receive(:upload).exactly(1).times
expect(subject.upload(from, to)).to eq(size)
end
end
describe ".powershell" do
it "should call winrm powershell" do
expect(shell).to receive(:run).with("dir").and_return(output)
@ -66,7 +95,7 @@ describe VagrantPlugins::CommunicatorWinRM::WinRMShell do
end
end
describe ".cmd" do
describe ".cmd" do
it "should call winrm cmd" do
expect(connection).to receive(:shell).with(:cmd, { })
expect(shell).to receive(:run).with("dir").and_return(output)

View File

@ -89,5 +89,39 @@ describe VagrantPlugins::FileUpload::Provisioner do
subject.provision
end
it "appends a '/.' if the destination doesnt end with a file separator" do
allow(config).to receive(:source).and_return("/source")
allow(config).to receive(:destination).and_return("/foo/bar")
allow(File).to receive(:directory?).with("/source").and_return(true)
expect(guest).to receive(:capability?).
with(:shell_expand_guest_path).and_return(true)
expect(guest).to receive(:capability).
with(:shell_expand_guest_path, "/foo/bar").and_return("/foo/bar")
expect(communicator).to receive(:upload).with("/source/.", "/foo/bar")
subject.provision
end
it "sends an array of files and folders if winrm and destination doesn't end with file separator" do
files = ["/source/file.py", "/source/folder"]
allow(Dir).to receive(:[]).and_return(files)
allow(config).to receive(:source).and_return("/source")
allow(config).to receive(:destination).and_return("/foo/bar")
allow(File).to receive(:directory?).with("/source").and_return(true)
allow(machine.config.vm).to receive(:communicator).and_return(:winrm)
expect(guest).to receive(:capability?).
with(:shell_expand_guest_path).and_return(true)
expect(guest).to receive(:capability).
with(:shell_expand_guest_path, "/foo/bar").and_return("/foo/bar")
expect(communicator).to receive(:upload)
.with(files, "/foo/bar")
subject.provision
end
end
end

View File

@ -25,6 +25,42 @@ new VM.
config.vm.provision "file", source: "~/.gitconfig", destination: ".gitconfig"
end
If you want to upload a folder to your guest system, it can be accomplished by
using a file provisioner seen below. When copied, the resulting folder on the guest will
replace `folder` as `newfolder` and place its on the guest machine. Note that if
you'd like the same folder name on your guest machine, make sure that the destination
path has the same name as the folder on your host.
Vagrant.configure("2") do |config|
# ... other configuration
config.vm.provision "file", source: "~/path/to/host/folder", destination: "$HOME/remote/newfolder"
end
Prior to copying `~/path/to/host/folder` to the guest machine:
folder
├── script.sh
├── otherfolder
│   └── hello.sh
├── goodbye.sh
├── hello.sh
└── woot.sh
1 directory, 5 files
After to copying `~/path/to/host/folder` into `$HOME/remote/newfolder` to the guest machine:
newfolder
├── script.sh
├── otherfolder
│   └── hello.sh
├── goodbye.sh
├── hello.sh
└── woot.sh
1 directory, 5 files
Note that, unlike with synced folders, files or directories that are uploaded
will not be kept in sync. Continuing with the example above, if you make
further changes to your local ~/.gitconfig, they will not be immediately
@ -49,3 +85,36 @@ The file provisioner takes only two options, both of which are required:
the source will be uploaded to. The file/folder is uploaded as the SSH user
over SCP, so this location must be writable to that user. The SSH user can be
determined by running `vagrant ssh-config`, and defaults to "vagrant".
## Caveats
While the file provisioner does support trailing slashes or "globing", this can
lead to some confusing results due to the underlying tool used to copy files and
folders between the host and guests. For example, if you have a source and
destination with a trailing slash defined below:
config.vm.provision "file", source: "~/pathfolder", destination: "/remote/newlocation/"
You are telling vagrant to upload `~/pathfolder` under the remote dir `/remote/newlocation`,
which will look like:
newlocation
├── pathfolder
│   └── file.sh
1 directory, 2 files
This behavior can also be achieved by defining your file provisioner below:
config.vm.provision "file", source: "~/pathfolder", destination: "/remote/newlocation/pathfolder"
Another example is using globing on the host machine to grab all files within a
folder, but not the top level folder itself:
config.vm.provision "file", source: "~/otherfolder/.", destination: "/remote/otherlocation"
The file provisioner is defined to include all files under `~/otherfolder`
to the new location `/remote/otherlocation`. This idea can be achieved by simply
having your destination folder differ from the source folder:
config.vm.provision "file", source: "/otherfolder", destination: "/remote/otherlocation"