From bdb88da74353c0d1a115d3597f5c033c49539aff Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 7 Jan 2014 20:39:24 -0800 Subject: [PATCH] hosts/bsd: convert to new style --- lib/vagrant/util.rb | 1 + plugins/hosts/bsd/cap/nfs.rb | 183 ++++++++++++++++++++ plugins/hosts/bsd/host.rb | 180 +------------------ plugins/hosts/bsd/plugin.rb | 25 +++ plugins/kernel_v2/config/vm.rb | 2 +- plugins/synced_folders/nfs/synced_folder.rb | 5 +- 6 files changed, 214 insertions(+), 182 deletions(-) create mode 100644 plugins/hosts/bsd/cap/nfs.rb diff --git a/lib/vagrant/util.rb b/lib/vagrant/util.rb index c6c954c65..db729bfdc 100644 --- a/lib/vagrant/util.rb +++ b/lib/vagrant/util.rb @@ -8,5 +8,6 @@ module Vagrant autoload :SafeExec, 'vagrant/util/safe_exec' autoload :StackedProcRunner, 'vagrant/util/stacked_proc_runner' autoload :TemplateRenderer, 'vagrant/util/template_renderer' + autoload :Subprocess, 'vagrant/util/subprocess' end end diff --git a/plugins/hosts/bsd/cap/nfs.rb b/plugins/hosts/bsd/cap/nfs.rb new file mode 100644 index 000000000..662985e38 --- /dev/null +++ b/plugins/hosts/bsd/cap/nfs.rb @@ -0,0 +1,183 @@ +require "log4r" + +require "vagrant/util" + +module VagrantPlugins + module HostBSD + module Cap + class NFS + extend Vagrant::Util::Retryable + + def self.nfs_export(environment, ui, id, ips, folders) + nfs_exports_template = environment.host.capability(:nfs_exports_template) + nfs_restart_command = environment.host.capability(:nfs_restart_command) + logger = Log4r::Logger.new("vagrant::hosts::bsd") + + nfs_checkexports! if File.file?("/etc/exports") + + # We need to build up mapping of directories that are enclosed + # within each other because the exports file has to have subdirectories + # of an exported directory on the same line. e.g.: + # + # "/foo" "/foo/bar" ... + # "/bar" + # + # We build up this mapping within the following hash. + logger.debug("Compiling map of sub-directories for NFS exports...") + dirmap = {} + folders.each do |_, opts| + hostpath = opts[:hostpath].dup + hostpath.gsub!('"', '\"') + + found = false + dirmap.each do |dirs, diropts| + dirs.each do |dir| + if dir.start_with?(hostpath) || hostpath.start_with?(dir) + # TODO: verify opts and diropts are _identical_, raise an error + # if not. NFS mandates subdirectories have identical options. + dirs << hostpath + found = true + break + end + end + + break if found + end + + if !found + dirmap[[hostpath]] = opts.dup + end + end + + # Sort all the keys by length so that the directory closest to + # the root is exported first. + dirmap.each do |dirs, _| + dirs.sort_by! { |d| d.length } + end + + # Setup the NFS options + dirmap.each do |dirs, opts| + if !opts[:bsd__nfs_options] + opts[:bsd__nfs_options] = ["alldirs"] + end + + hasmapall = false + opts[:bsd__nfs_options].each do |opt| + # mapall/maproot are mutually exclusive, so we have to check + # for both here. + if opt =~ /^mapall=/ || opt =~ /^maproot=/ + hasmapall = true + break + end + end + + if !hasmapall + opts[:bsd__nfs_options] << "mapall=#{opts[:map_uid]}:#{opts[:map_gid]}" + end + + opts[:bsd__compiled_nfs_options] = opts[:bsd__nfs_options].map do |opt| + "-#{opt}" + end.join(" ") + end + + logger.info("Exporting the following for NFS...") + dirmap.each do |dirs, opts| + logger.info("NFS DIR: #{dirs.inspect}") + logger.info("NFS OPTS: #{opts.inspect}") + end + + output = Vagrant::Util::TemplateRenderer.render(nfs_exports_template, + :uuid => id, + :ips => ips, + :folders => dirmap, + :user => Process.uid) + + # The sleep ensures that the output is truly flushed before any `sudo` + # commands are issued. + ui.info I18n.t("vagrant.hosts.bsd.nfs_export") + sleep 0.5 + + # First, clean up the old entry + nfs_cleanup(id) + + # Output the rendered template into the exports + output.split("\n").each do |line| + line.gsub!('"', '\"') + line.gsub!("'", "'\\\\''") + system(%Q[sudo -s -- "echo '#{line}' >> /etc/exports"]) + end + + # We run restart here instead of "update" just in case nfsd + # is not starting + system(nfs_restart_command) + end + + def self.nfs_exports_template(environment) + "nfs/exports" + end + + def self.nfs_installed(environment) + retryable(:tries => 10, :on => TypeError) do + system("which nfsd > /dev/null 2>&1") + end + end + + def self.nfs_prune(environment, ui, valid_ids) + return if !File.exist?("/etc/exports") + + logger = Log4r::Logger.new("vagrant::hosts::bsd") + logger.info("Pruning invalid NFS entries...") + + output = false + user = Process.uid + + File.read("/etc/exports").lines.each do |line| + if id = line[/^# VAGRANT-BEGIN:( #{user})? ([A-Za-z0-9-]+?)$/, 2] + if valid_ids.include?(id) + logger.debug("Valid ID: #{id}") + else + if !output + # We want to warn the user but we only want to output once + ui.info I18n.t("vagrant.hosts.bsd.nfs_prune") + output = true + end + + logger.info("Invalid ID, pruning: #{id}") + nfs_cleanup(id) + end + end + end + rescue Errno::EACCES + raise Vagrant::Errors::NFSCantReadExports + end + + def self.nfs_restart_command(environment) + "sudo nfsd restart" + end + + protected + + def self.nfs_cleanup(id) + return if !File.exist?("/etc/exports") + + # Escape sed-sensitive characters: + id = id.gsub("/", "\\/") + id = id.gsub(".", "\\.") + + user = Process.uid + + # Use sed to just strip out the block of code which was inserted + # by Vagrant, and restart NFS. + system("sudo sed -E -e '/^# VAGRANT-BEGIN:( #{user})? #{id}/,/^# VAGRANT-END:( #{user})? #{id}/ d' -ibak /etc/exports") + end + + def self.nfs_checkexports! + r = Vagrant::Util::Subprocess.execute("nfsd", "checkexports") + if r.exit_code != 0 + raise Vagrant::Errors::NFSBadExports, output: r.stderr + end + end + end + end + end +end diff --git a/plugins/hosts/bsd/host.rb b/plugins/hosts/bsd/host.rb index f67f8b801..88d28a7ec 100644 --- a/plugins/hosts/bsd/host.rb +++ b/plugins/hosts/bsd/host.rb @@ -1,190 +1,12 @@ -require 'log4r' - require "vagrant" -require 'vagrant/util/platform' -require "vagrant/util/subprocess" module VagrantPlugins module HostBSD # Represents a BSD host, such as FreeBSD and Darwin (Mac OS X). class Host < Vagrant.plugin("2", :host) - include Vagrant::Util - include Vagrant::Util::Retryable - - def self.match? + def detect?(env) Vagrant::Util::Platform.darwin? || Vagrant::Util::Platform.bsd? end - - def self.precedence - # Set a lower precedence because this is a generic OS. We - # want specific distros to match first. - 2 - end - - def initialize(*args) - super - - @logger = Log4r::Logger.new("vagrant::hosts::bsd") - @nfs_restart_command = "sudo nfsd restart" - @nfs_exports_template = "nfs/exports" - end - - def nfs? - retryable(:tries => 10, :on => TypeError) do - system("which nfsd > /dev/null 2>&1") - end - end - - def nfs_export(id, ips, folders) - nfs_checkexports! if File.file?("/etc/exports") - - # We need to build up mapping of directories that are enclosed - # within each other because the exports file has to have subdirectories - # of an exported directory on the same line. e.g.: - # - # "/foo" "/foo/bar" ... - # "/bar" - # - # We build up this mapping within the following hash. - @logger.debug("Compiling map of sub-directories for NFS exports...") - dirmap = {} - folders.each do |_, opts| - hostpath = opts[:hostpath].dup - hostpath.gsub!('"', '\"') - - found = false - dirmap.each do |dirs, diropts| - dirs.each do |dir| - if dir.start_with?(hostpath) || hostpath.start_with?(dir) - # TODO: verify opts and diropts are _identical_, raise an error - # if not. NFS mandates subdirectories have identical options. - dirs << hostpath - found = true - break - end - end - - break if found - end - - if !found - dirmap[[hostpath]] = opts.dup - end - end - - # Sort all the keys by length so that the directory closest to - # the root is exported first. - dirmap.each do |dirs, _| - dirs.sort_by! { |d| d.length } - end - - # Setup the NFS options - dirmap.each do |dirs, opts| - if !opts[:bsd__nfs_options] - opts[:bsd__nfs_options] = ["alldirs"] - end - - hasmapall = false - opts[:bsd__nfs_options].each do |opt| - # mapall/maproot are mutually exclusive, so we have to check - # for both here. - if opt =~ /^mapall=/ || opt =~ /^maproot=/ - hasmapall = true - break - end - end - - if !hasmapall - opts[:bsd__nfs_options] << "mapall=#{opts[:map_uid]}:#{opts[:map_gid]}" - end - - opts[:bsd__compiled_nfs_options] = opts[:bsd__nfs_options].map do |opt| - "-#{opt}" - end.join(" ") - end - - @logger.info("Exporting the following for NFS...") - dirmap.each do |dirs, opts| - @logger.info("NFS DIR: #{dirs.inspect}") - @logger.info("NFS OPTS: #{opts.inspect}") - end - - output = TemplateRenderer.render(@nfs_exports_template, - :uuid => id, - :ips => ips, - :folders => dirmap, - :user => Process.uid) - - # The sleep ensures that the output is truly flushed before any `sudo` - # commands are issued. - @ui.info I18n.t("vagrant.hosts.bsd.nfs_export") - sleep 0.5 - - # First, clean up the old entry - nfs_cleanup(id) - - # Output the rendered template into the exports - output.split("\n").each do |line| - line.gsub!('"', '\"') - line.gsub!("'", "'\\\\''") - system(%Q[sudo -s -- "echo '#{line}' >> /etc/exports"]) - end - - # We run restart here instead of "update" just in case nfsd - # is not starting - system(@nfs_restart_command) - end - - def nfs_prune(valid_ids) - return if !File.exist?("/etc/exports") - - @logger.info("Pruning invalid NFS entries...") - - output = false - user = Process.uid - - File.read("/etc/exports").lines.each do |line| - if id = line[/^# VAGRANT-BEGIN:( #{user})? ([A-Za-z0-9-]+?)$/, 2] - if valid_ids.include?(id) - @logger.debug("Valid ID: #{id}") - else - if !output - # We want to warn the user but we only want to output once - @ui.info I18n.t("vagrant.hosts.bsd.nfs_prune") - output = true - end - - @logger.info("Invalid ID, pruning: #{id}") - nfs_cleanup(id) - end - end - end - rescue Errno::EACCES - raise Vagrant::Errors::NFSCantReadExports - end - - protected - - def nfs_checkexports! - r = Subprocess.execute("nfsd", "checkexports") - if r.exit_code != 0 - raise Vagrant::Errors::NFSBadExports, output: r.stderr - end - end - - def nfs_cleanup(id) - return if !File.exist?("/etc/exports") - - # Escape sed-sensitive characters: - id = id.gsub("/", "\\/") - id = id.gsub(".", "\\.") - - user = Process.uid - - # Use sed to just strip out the block of code which was inserted - # by Vagrant, and restart NFS. - system("sudo sed -E -e '/^# VAGRANT-BEGIN:( #{user})? #{id}/,/^# VAGRANT-END:( #{user})? #{id}/ d' -ibak /etc/exports") - end end end end diff --git a/plugins/hosts/bsd/plugin.rb b/plugins/hosts/bsd/plugin.rb index 781493af2..359d4f7ad 100644 --- a/plugins/hosts/bsd/plugin.rb +++ b/plugins/hosts/bsd/plugin.rb @@ -10,6 +10,31 @@ module VagrantPlugins require File.expand_path("../host", __FILE__) Host end + + host_capability("bsd", "nfs_export") do + require_relative "cap/nfs" + Cap::NFS + end + + host_capability("bsd", "nfs_exports_template") do + require_relative "cap/nfs" + Cap::NFS + end + + host_capability("bsd", "nfs_installed") do + require_relative "cap/nfs" + Cap::NFS + end + + host_capability("bsd", "nfs_prune") do + require_relative "cap/nfs" + Cap::NFS + end + + host_capability("bsd", "nfs_restart_command") do + require_relative "cap/nfs" + Cap::NFS + end end end end diff --git a/plugins/kernel_v2/config/vm.rb b/plugins/kernel_v2/config/vm.rb index e2325a719..7f62b7ed2 100644 --- a/plugins/kernel_v2/config/vm.rb +++ b/plugins/kernel_v2/config/vm.rb @@ -459,7 +459,7 @@ module VagrantPlugins errors << I18n.t("vagrant.config.vm.nfs_requires_host") else errors << I18n.t("vagrant.config.vm.nfs_not_supported") if \ - !machine.env.host.nfs? + !machine.env.host.capability(:nfs_installed) end end diff --git a/plugins/synced_folders/nfs/synced_folder.rb b/plugins/synced_folders/nfs/synced_folder.rb index 5e7f2292c..c2b43cf37 100644 --- a/plugins/synced_folders/nfs/synced_folder.rb +++ b/plugins/synced_folders/nfs/synced_folder.rb @@ -49,7 +49,8 @@ module VagrantPlugins # overlapping input requests. [GH-2680] @@lock.synchronize do machine.ui.info I18n.t("vagrant.actions.vm.nfs.exporting") - machine.env.host.nfs_export(machine.id, machine_ip, folders) + machine.env.host.capability(:nfs_export, + machine.ui, machine.id, machine_ip, folders) end # Mount @@ -72,7 +73,7 @@ module VagrantPlugins # Prune any of the unused machines @logger.info("NFS pruning. Valid IDs: #{ids.inspect}") - machine.env.host.nfs_prune(ids) + machine.env.host.capability(:nfs_prune, machine.ui, ids) end protected