synced_folders/smb: basically working

This commit is contained in:
Mitchell Hashimoto 2014-02-26 15:54:53 -08:00
parent 31abc3f4a3
commit 0fe4a4af26
13 changed files with 421 additions and 100 deletions

View File

@ -0,0 +1,20 @@
module VagrantPlugins
module GuestLinux
module Cap
module ChooseAddressableIPAddr
def self.choose_addressable_ip_addr(machine, possible)
machine.communicate.tap do |comm|
possible.each do |ip|
command = "ping -c1 -w1 -W1 #{ip}"
if comm.test(command)
return ip
end
end
end
nil
end
end
end
end
end

View File

@ -0,0 +1,80 @@
module VagrantPlugins
module GuestLinux
module Cap
class MountSMBSharedFolder
def self.mount_smb_shared_folder(machine, name, guestpath, options)
expanded_guest_path = machine.guest.capability(
:shell_expand_guest_path, guestpath)
mount_commands = []
mount_device = "//#{options[:smb_host]}/#{name}"
if options[:owner].is_a? Integer
mount_uid = options[:owner]
else
mount_uid = "`id -u #{options[:owner]}`"
end
if options[:group].is_a? Integer
mount_gid = options[:group]
mount_gid_old = options[:group]
else
mount_gid = "`getent group #{options[:group]} | cut -d: -f3`"
mount_gid_old = "`id -g #{options[:group]}`"
end
options[:mount_options] ||= []
options[:mount_options] << "sec=ntlm"
options[:mount_options] << "username=#{options[:smb_username]}"
options[:mount_options] << "pass=#{options[:smb_password]}"
# First mount command uses getent to get the group
mount_options = "-o uid=#{mount_uid},gid=#{mount_gid}"
mount_options += ",#{options[:mount_options].join(",")}" if options[:mount_options]
mount_commands << "mount -t cifs #{mount_options} #{mount_device} #{expanded_guest_path}"
# Second mount command uses the old style `id -g`
mount_options = "-o uid=#{mount_uid},gid=#{mount_gid_old}"
mount_options += ",#{options[:mount_options].join(",")}" if options[:mount_options]
mount_commands << "mount -t cifs #{mount_options} #{mount_device} #{expanded_guest_path}"
# Create the guest path if it doesn't exist
machine.communicate.sudo("mkdir -p #{expanded_guest_path}")
# Attempt to mount the folder. We retry here a few times because
# it can fail early on.
attempts = 0
while true
success = true
mount_commands.each do |command|
no_such_device = false
status = machine.communicate.sudo(command, error_check: false) do |type, data|
no_such_device = true if type == :stderr && data =~ /No such device/i
end
success = status == 0 && !no_such_device
break if success
end
break if success
attempts += 1
if attempts > 10
raise Vagrant::Errors::LinuxMountFailed,
command: mount_commands.join("\n")
end
sleep 2
end
# Emit an upstart event if we can
if machine.communicate.test("test -x /sbin/initctl")
machine.communicate.sudo(
"/sbin/initctl emit --no-wait vagrant-mounted MOUNTPOINT=#{expanded_guest_path}")
end
end
end
end
end
end

View File

@ -11,6 +11,11 @@ module VagrantPlugins
Guest Guest
end end
guest_capability("linux", "choose_addressable_ip_addr") do
require_relative "cap/choose_addressable_ip_addr"
Cap::ChooseAddressableIPAddr
end
guest_capability("linux", "halt") do guest_capability("linux", "halt") do
require_relative "cap/halt" require_relative "cap/halt"
Cap::Halt Cap::Halt
@ -31,6 +36,11 @@ module VagrantPlugins
Cap::MountNFS Cap::MountNFS
end end
guest_capability("linux", "mount_smb_shared_folder") do
require_relative "cap/mount_smb_shared_folder"
Cap::MountSMBSharedFolder
end
guest_capability("linux", "mount_virtualbox_shared_folder") do guest_capability("linux", "mount_virtualbox_shared_folder") do
require_relative "cap/mount_virtualbox_shared_folder" require_relative "cap/mount_virtualbox_shared_folder"
Cap::MountVirtualBoxSharedFolder Cap::MountVirtualBoxSharedFolder

View File

@ -16,14 +16,9 @@ module VagrantPlugins
b2.use MessageNotCreated b2.use MessageNotCreated
next next
end end
b2.use action_halt b2.use action_halt
b2.use Call, WaitForState, :off, 120 do |env2, b3| b2.use action_start
if env2[:result]
b3.use action_up
else
env2[:ui].info("Machine did not reload, Check machine's status")
end
end
end end
end end
end end
@ -58,7 +53,12 @@ module VagrantPlugins
b2.use MessageNotCreated b2.use MessageNotCreated
next next
end end
b2.use StopInstance
b2.use Call, GracefulHalt, :off, :running do |env2, b3|
if !env2[:result]
b3.use StopInstance
end
end
end end
end end
end end
@ -69,6 +69,8 @@ module VagrantPlugins
b.use StartInstance b.use StartInstance
b.use WaitForIPAddress b.use WaitForIPAddress
b.use WaitForCommunicator, [:running] b.use WaitForCommunicator, [:running]
b.use SyncedFolders
#b.use ShareFolders #b.use ShareFolders
#b.use SyncFolders #b.use SyncFolders
end end
@ -79,18 +81,11 @@ module VagrantPlugins
b.use HandleBox b.use HandleBox
b.use ConfigValidate b.use ConfigValidate
b.use Call, IsCreated do |env1, b1| b.use Call, IsCreated do |env1, b1|
if env1[:result] if !env1[:result]
b1.use Call, IsStopped do |env2, b2|
if env2[:result]
b2.use action_start
else
b2.use MessageAlreadyCreated
end
end
else
b1.use Import b1.use Import
b1.use action_start
end end
b1.use action_start
end end
end end
end end
@ -145,7 +140,6 @@ module VagrantPlugins
autoload :ReadGuestIP, action_root.join('read_guest_ip') autoload :ReadGuestIP, action_root.join('read_guest_ip')
autoload :ShareFolders, action_root.join('share_folders') autoload :ShareFolders, action_root.join('share_folders')
autoload :WaitForIPAddress, action_root.join("wait_for_ip_address") autoload :WaitForIPAddress, action_root.join("wait_for_ip_address")
autoload :WaitForState, action_root.join('wait_for_state')
end end
end end
end end

View File

@ -7,7 +7,7 @@ module VagrantPlugins
end end
def call(env) def call(env)
env[:ui].info("Stopping the machine...")) env[:ui].info("Stopping the machine...")
options = { VmId: env[:machine].id } options = { VmId: env[:machine].id }
env[:machine].provider.driver.execute('stop_vm.ps1', options) env[:machine].provider.driver.execute('stop_vm.ps1', options)
@app.call(env) @app.call(env)

View File

@ -1,40 +0,0 @@
#-------------------------------------------------------------------------
# Copyright (c) Microsoft Open Technologies, Inc.
# All Rights Reserved. Licensed under the MIT License.
#--------------------------------------------------------------------------
require "log4r"
require "timeout"
require "debugger"
module VagrantPlugins
module HyperV
module Action
class WaitForState
def initialize(app, env, state, timeout)
@app = app
@state = state
@timeout = timeout
end
def call(env)
env[:result] = true
# Wait until the Machine's state is disabled (ie State of Halt)
unless env[:machine].state.id == @state
env[:ui].info("Waiting for machine to #{@state}")
begin
Timeout.timeout(@timeout) do
until env[:machine].state.id == @state
sleep 2
end
end
rescue Timeout::Error
env[:result] = false # couldn't reach state in time
end
end
@app.call(env)
end
end
end
end
end

View File

@ -1,46 +1,30 @@
#------------------------------------------------------------------------- Param(
# Copyright (c) Microsoft Open Technologies, Inc. [Parameter(Mandatory=$true)]
# All Rights Reserved. Licensed under the MIT License. [string]$path,
#-------------------------------------------------------------------------- [Parameter(Mandatory=$true)]
[string]$share_name,
[Parameter(Mandatory=$true)]
[string]$host_share_username
)
param ( $ErrorAction = "Stop"
[string]$path = $(throw "-path is required."),
[string]$share_name = $(throw "-share_name is required."),
[string]$host_share_username = $(throw "-host_share_username is required")
)
# Include the following modules # See all available shares and check alert user for
$presentDir = Split-Path -parent $PSCommandPath # existing/conflicting share name
$modules = @() $shared_folders = net share
$modules += $presentDir + "\utils\write_messages.ps1" $reg = "$share_name(\s+)$path(\s)"
forEach ($module in $modules) { . $module } $existing_share = $shared_folders -Match $reg
if ($existing_share) {
try {
# See all available shares and check alert user for existing / conflicting share name
$shared_folders = net share
$reg = "$share_name(\s+)$path(\s)"
$existing_share = $shared_folders -Match $reg
if ($existing_share) {
# Always clear the existing share name and create a new one # Always clear the existing share name and create a new one
net share $share_name /delete /y net share $share_name /delete /y
}
$computer_name = $(Get-WmiObject Win32_Computersystem).name
$grant_permission = "$computer_name\$host_share_username,Full"
$result = net share $share_name=$path /unlimited /GRANT:$grant_permission
if ($result -Match "$share_name was shared successfully.") {
$resultHash = @{
message = "OK"
}
$result = ConvertTo-Json $resultHash
Write-Output-Message $result
} else {
$reg = "^$share_name(\s+)"
$existing_share = $shared_folders -Match $reg
Write-Error-Message "IGNORING Conflicting share name, A share name already exist $existing_share"
}
} catch {
Write-Error-Message $_
return
} }
$computer_name = $(Get-WmiObject Win32_Computersystem).name
$grant_permission = "$computer_name\$host_share_username,Full"
$result = net share $share_name=$path /unlimited /GRANT:$grant_permission
if ($result -Match "$share_name was shared successfully.") {
exit 0
}
$host.ui.WriteErrorLine("Error: $result")
exit 1

View File

@ -0,0 +1,22 @@
module VagrantPlugins
module SyncedFolderSMB
module Errors
# A convenient superclass for all our errors.
class SMBError < Vagrant::Errors::VagrantError
error_namespace("vagrant_sf_smb.errors")
end
class DefineShareFailed < SMBError
error_key(:define_share_failed)
end
class NoHostIPAddr < SMBError
error_key(:no_routable_host_addr)
end
class PowershellError < SMBError
error_key(:powershell_error)
end
end
end
end

View File

@ -0,0 +1,32 @@
require "vagrant"
module VagrantPlugins
module SyncedFolderSMB
autoload :Errors, File.expand_path("../errors", __FILE__)
# This plugin implements SMB synced folders.
class Plugin < Vagrant.plugin("2")
name "SMB synced folders"
description <<-EOF
The SMB synced folders plugin enables you to use SMB folders on
Windows and share them to guest machines.
EOF
synced_folder("smb", 5) do
require_relative "synced_folder"
init!
SyncedFolder
end
protected
def self.init!
return if defined?(@_init)
I18n.load_path << File.expand_path(
"templates/locales/synced_folder_smb.yml", Vagrant.source_root)
I18n.reload!
@_init = true
end
end
end
end

View File

@ -0,0 +1,8 @@
$ErrorAction = "Stop"
$net = Get-WmiObject -class win32_NetworkAdapterConfiguration -Filter 'ipenabled = "true"'
$result = @{
ip_addresses = $net.ipaddress
}
Write-Output $(ConvertTo-Json $result)

View File

@ -0,0 +1,34 @@
Param(
[Parameter(Mandatory=$true)]
[string]$path,
[Parameter(Mandatory=$true)]
[string]$share_name,
[string]$host_share_username = $null
)
$ErrorAction = "Stop"
# See all available shares and check alert user for existing/conflicting
# share names.
$path_regexp = [System.Text.RegularExpressions.Regex]::Escape($path)
$name_regexp = [System.Text.RegularExpressions.Regex]::Escape($share_name)
$reg = "(?m)$name_regexp\s+$path_regexp\s"
$existing_share = $($(net share) -join "`n") -Match $reg
if ($existing_share) {
# Always clear the existing share name and create a new one
net share $share_name /delete /y
}
$grant = "Everyone,Full"
if (![string]::IsNullOrEmpty($host_share_username)) {
$computer_name = $(Get-WmiObject Win32_Computersystem).name
$grant = "$computer_name\$host_share_username,Full"
}
$result = net share $share_name=$path /unlimited /GRANT:$grant
if ($result -Match "$share_name was shared successfully.") {
exit 0
}
$host.ui.WriteErrorLine("Error: $result")
exit 1

View File

@ -0,0 +1,138 @@
require "json"
require "log4r"
require "vagrant/util/platform"
require "vagrant/util/powershell"
module VagrantPlugins
module SyncedFolderSMB
class SyncedFolder < Vagrant.plugin("2", :synced_folder)
def initialize(*args)
super
@logger = Log4r::Logger.new("vagrant::synced_folders::smb")
end
def usable?(machine, raise_error=false)
if !Vagrant::Util::Platform.windows?
# TODO: raise error if specified
return false
end
true
end
def prepare(machine, folders, opts)
script_path = File.expand_path("../scripts/set_share.ps1", __FILE__)
folders.each do |id, data|
hostpath = data[:hostpath]
data[:smb_id] ||= "#{machine.id}-#{id.gsub("/", "-")}"
args = []
args << "-path" << hostpath.gsub("/", "\\")
args << "-share_name" << data[:smb_id]
#args << "-host_share_username" << "mitchellh"
r = Vagrant::Util::PowerShell.execute(script_path, *args)
if r.exit_code != 0
raise Errors::DefineShareFailed,
host: hostpath.to_s,
stderr: r.stderr,
stdout: r.stdout
end
end
end
def enable(machine, folders, nfsopts)
machine.ui.output(I18n.t("vagrant_sf_smb.mounting"))
# Make sure that this machine knows this dance
if !machine.guest.capability?(:mount_smb_shared_folder)
raise Vagrant::Errors::GuestCapabilityNotFound,
cap: "mount_smb_shared_folder",
guest: machine.guest.name.to_s
end
# Detect the host IP for this guest if one wasn't specified
# for every folder.
host_ip = nil
need_host_ip = false
folders.each do |id, data|
if !data[:smb_host]
need_host_ip = true
break
end
end
if need_host_ip
candidate_ips = load_host_ips
@logger.debug("Potential host IPs: #{candidate_ips.inspect}")
host_ip = machine.guest.capability(
:choose_addressable_ip_addr, candidate_ips)
if !host_ip
raise Errors::NoHostIPAddr
end
end
# If we need auth information, then ask the user
username = nil
password = nil
need_auth = false
folders.each do |id, data|
if !data[:smb_username] || !data[:smb_password]
need_auth = true
break
end
end
if need_auth
machine.ui.detail(I18n.t("vagrant_sf_smb.warning_password") + "\n ")
username = machine.ui.ask("Username: ")
password = machine.ui.ask("Password (will be hidden): ", echo: false)
end
# This is used for defaulting the owner/group
ssh_info = machine.ssh_info
folders.each do |id, data|
data = data.dup
data[:smb_host] ||= host_ip
data[:smb_username] ||= username
data[:smb_password] ||= password
# Default the owner/group of the folder to the SSH user
data[:owner] ||= ssh_info[:username]
data[:group] ||= ssh_info[:username]
machine.ui.detail(I18n.t(
"vagrant_sf_smb.mounting_single",
host: data[:hostpath].to_s,
guest: data[:guestpath].to_s))
machine.guest.capability(
:mount_smb_shared_folder, data[:smb_id], data[:guestpath], data)
end
end
def cleanup(machine, opts)
end
protected
def load_host_ips
script_path = File.expand_path("../scripts/host_info.ps1", __FILE__)
r = Vagrant::Util::PowerShell.execute(script_path)
if r.exit_code != 0
raise Errors::PowershellError,
script: script_path,
stderr: r.stderr
end
JSON.parse(r.stdout)["ip_addresses"]
end
end
end
end

View File

@ -0,0 +1,39 @@
en:
vagrant_sf_smb:
mounting: |-
Mounting SMB shared folders...
mounting_single: |-
%{host} => %{guest}
warning_password: |-
You will be asked for the username and password to use to mount the
folders shortly. Please use the proper username/password of your
Windows account.
errors:
define_share_failed: |-
Exporting an SMB share failed! Details about the failure are shown
below. Please inspect the error message and correct any problems.
Host path: %{host}
Stderr: %{stderr}
Stdout: %{stdout}
no_routable_host_addr: |-
We couldn't detect an IP address that was routable to this
machine from the guest machine! Please verify networking is properly
setup in the guest machine and that it is able to access this
host.
As another option, you can manually specify an IP for the machine
to mount from using the `smb_host` option to the synced folder.
powershell_error: |-
An error occurred while executing a PowerShell script. This error
is shown below. Please read the error message and see if this is
a configuration error with your system. If it is not, then please
report a bug.
Script: %{script}
Error:
%{stderr}