require "digest/md5" require "securerandom" require "log4r" require "vagrant/action/builtin/mixin_synced_folders" module VagrantPlugins module DockerProvider module Action # This action is responsible for creating the host machine if # we need to. The host machine is where Docker containers will # live. class HostMachineSyncFolders include Vagrant::Action::Builtin::MixinSyncedFolders def initialize(app, env) @app = app @logger = Log4r::Logger.new("vagrant::docker::hostmachine") end def call(env) return @app.call(env) if !env[:machine].provider.host_vm? if !env.has_key?(:host_machine_sync_folders) env[:host_machine_sync_folders] = true end host_machine = env[:machine].provider.host_vm # Lock while we make changes begin env[:machine].provider.host_vm_lock do setup_synced_folders(host_machine, env) end rescue Vagrant::Errors::EnvironmentLockedError sleep 1 retry end @app.call(env) end protected def setup_synced_folders(host_machine, env) # Write the host machine SFID if we have one id_path = env[:machine].data_dir.join("host_machine_sfid") host_sfid = nil if !id_path.file? host_sfid = SecureRandom.uuid id_path.open("w") do |f| f.binmode f.write("#{host_sfid}\n") end else host_sfid = id_path.read.chomp end # Create a UI for this machine that stays at the detail level proxy_ui = host_machine.ui.dup proxy_ui.opts[:bold] = false proxy_ui.opts[:prefix_spaces] = true proxy_ui.opts[:target] = env[:machine].name.to_s # Read the existing folders that are setup existing_folders = synced_folders(host_machine, cached: true) existing_ids = {} if existing_folders existing_folders.each do |impl, fs| fs.each do |_name, data| if data[:docker_sfid] && data[:docker_host_sfid] == host_sfid existing_ids[data[:docker_sfid]] = data end end end end # Sync some folders so that our volumes work later. new_config = VagrantPlugins::Kernel_V2::VMConfig.new our_folders = synced_folders(env[:machine]) our_folders.each do |type, folders| folders.each do |id, data| data = data.dup if type == :docker # We don't use the Docker type explicitly on the host VM data.delete(:type) end # Expand the hostpath relative to _our_ root path. Otherwise, # it expands it relative to the proxy VM, which is not what # we want. data[:hostpath] = File.expand_path( data[:hostpath], env[:machine].env.root_path) # Generate an ID that is deterministic based on our machine # and Vagrantfile path... id = Digest::MD5.hexdigest( "#{env[:machine].env.root_path}" + "#{data[:hostpath]}" + "#{data[:guestpath]}" + "#{env[:machine].name}") # Generate a new guestpath data[:docker_guestpath] = data[:guestpath] data[:docker_sfid] = id data[:docker_host_sfid] = host_sfid data[:id] = id[0...6] + rand(10000).to_s # If we specify exact then we know what we're doing if !data[:docker__exact] data[:guestpath] = "/mnt/docker_#{Time.now.to_i}_#{rand(100000)}" end # Add this synced folder onto the new config if we haven't # already shared it before. if !existing_ids.has_key?(id) new_config.synced_folder( data[:hostpath], data[:guestpath], data) else # We already have the folder, so just load its data data = existing_ids[id] end # Remove from our machine env[:machine].config.vm.synced_folders.delete(id) # Add the "fixed" folder to our machine data = data.merge({ hostpath_exact: true, type: :docker, }) env[:machine].config.vm.synced_folder( data[:guestpath], data[:docker_guestpath], data) end end if !env[:host_machine_sync_folders] @logger.info("Not syncing folders because container created.") return end if !new_config.synced_folders.empty? # Sync the folders! env[:machine].ui.output(I18n.t( "docker_provider.host_machine_syncing_folders")) host_machine.with_ui(proxy_ui) do action_env = { synced_folders_config: new_config } begin host_machine.action(:sync_folders, action_env) rescue Vagrant::Errors::MachineActionLockedError sleep 1 retry rescue Vagrant::Errors::UnimplementedProviderAction callable = Vagrant::Action::Builder.new callable.use Vagrant::Action::Builtin::SyncedFolders host_machine.action_raw(:sync_folders, callable, action_env) end end end end end end end end