vagrant/plugins/providers/docker/provider.rb

214 lines
6.6 KiB
Ruby

require "digest/md5"
require "fileutils"
require "thread"
require "log4r"
require "vagrant/util/silence_warnings"
module VagrantPlugins
module DockerProvider
class Provider < Vagrant.plugin("2", :provider)
@@host_vm_mutex = Mutex.new
def initialize(machine)
@logger = Log4r::Logger.new("vagrant::provider::docker")
@machine = machine
if host_vm?
# We need to use a special communicator that proxies our
# SSH requests over our host VM to the container itself.
@machine.config.vm.communicator = :docker_hostvm
end
end
# @see Vagrant::Plugin::V2::Provider#action
def action(name)
action_method = "action_#{name}"
return Action.send(action_method) if Action.respond_to?(action_method)
nil
end
# Returns the driver instance for this provider.
def driver
if !@driver
if @machine.provider_config.compose
@driver = Driver::Compose.new(@machine)
else
@driver = Driver.new
end
end
if host_vm?
@driver.executor = Executor::Vagrant.new(host_vm)
end
@driver
end
def usable?(raise_error=false)
driver.execute("docker", "version")
true
rescue Vagrant::Errors::CommandUnavailable, Errors::ExecuteError
raise if raise_error
return false
end
# This returns the {Vagrant::Machine} that is our host machine.
# It does not perform any action on the machine or verify it is
# running.
#
# @return [Vagrant::Machine]
def host_vm
return @host_vm if @host_vm
vf_path = @machine.provider_config.vagrant_vagrantfile
host_machine_name = @machine.provider_config.vagrant_machine || :default
if !vf_path
# We don't have a Vagrantfile path set, so we're going to use
# the default but we need to copy it into the data dir so that
# we don't write into our installation dir (we can't).
default_path = File.expand_path("../hostmachine/Vagrantfile", __FILE__)
vf_path = @machine.env.data_dir.join("docker-host", "Vagrantfile")
begin
@machine.env.lock("docker-provider-hostvm") do
vf_path.dirname.mkpath
FileUtils.cp(default_path, vf_path)
end
rescue Vagrant::Errors::EnvironmentLockedError
# Lock contention, just retry
retry
end
# Set the machine name since we hardcode that for the default
host_machine_name = :default
end
# Expand it so that the home directories and so on get processed
# properly.
vf_path = File.expand_path(vf_path, @machine.env.root_path)
vf_file = File.basename(vf_path)
vf_path = File.dirname(vf_path)
# Create the env to manage this machine
@host_vm = Vagrant::Util::SilenceWarnings.silence! do
host_env = Vagrant::Environment.new(
cwd: vf_path,
home_path: @machine.env.home_path,
ui_class: @machine.env.ui_class,
vagrantfile_name: vf_file,
)
# If there is no root path, then the Vagrantfile wasn't found
# and it is an error...
raise Errors::VagrantfileNotFound if !host_env.root_path
host_env.machine(
host_machine_name,
host_env.default_provider(
exclude: [:docker],
force_default: false,
))
end
@host_vm
end
# This acquires a lock on the host VM.
def host_vm_lock
hash = Digest::MD5.hexdigest(host_vm.data_dir.to_s)
# We do a process-level mutex on the outside, since we can
# wait for that a short amount of time. Then, we do a process lock
# on the inside, which will raise an exception if locked.
host_vm_mutex.synchronize do
@machine.env.lock(hash) do
return yield
end
end
end
# This is a process-local mutex that can be used by parallel
# providers to lock the host VM access.
def host_vm_mutex
@@host_vm_mutex
end
# This says whether or not Docker will be running within a VM
# rather than directly on our system. Docker needs to run in a VM
# when we're not on Linux, or not on a Linux that supports Docker.
def host_vm?
@machine.provider_config.force_host_vm
end
# Returns the SSH info for accessing the Container.
def ssh_info
# If the container isn't running, we can't SSH into it
return nil if state.id != :running
port_name = "#{@machine.config.ssh.guest_port}/tcp"
network = driver.inspect_container(@machine.id)['NetworkSettings']
if network["Ports"][port_name].respond_to?(:first)
port_info = network["Ports"][port_name].first
else
ip = network["IPAddress"]
port = @machine.config.ssh.guest_port
if !ip.to_s.empty?
port_info = {
"HostIp" => ip,
"HostPort" => port
}
end
end
# If we were not able to identify the container's IP, we return nil
# here and we let Vagrant core deal with it ;)
return nil if port_info.nil? || port_info.empty?
{
host: port_info['HostIp'],
port: port_info['HostPort']
}
end
def state
state_id = nil
state_id = :not_created if !@machine.id
begin
state_id = :host_state_unknown if !state_id && \
host_vm? && !host_vm.communicate.ready?
rescue Errors::VagrantfileNotFound
state_id = :host_state_unknown
end
state_id = :not_created if !state_id && \
(!@machine.id || !driver.created?(@machine.id))
state_id = driver.state(@machine.id) if @machine.id && !state_id
state_id = :unknown if !state_id
# This is a special pseudo-state so that we don't set the
# NOT_CREATED_ID while we're setting up the machine. This avoids
# clearing the data dir.
state_id = :preparing if @machine.id == "preparing"
short = state_id.to_s.gsub("_", " ")
long = I18n.t("docker_provider.status.#{state_id}")
# If we're not created, then specify the special ID flag
if state_id == :not_created
state_id = Vagrant::MachineState::NOT_CREATED_ID
end
Vagrant::MachineState.new(state_id, short, long)
end
def to_s
id = @machine.id ? @machine.id : "new container"
"Docker (#{id})"
end
end
end
end