providers/docker: support building Dockerfiles

This commit is contained in:
Mitchell Hashimoto 2014-04-18 15:53:12 -07:00
parent 3ba0ca3562
commit 81df70eee0
12 changed files with 145 additions and 9 deletions

View File

@ -34,7 +34,6 @@ module Vagrant
# If this is our scope, then override
if parts[0] == scope
result[parts[1].to_sym] = value
result.delete(key)
end
end

View File

@ -107,6 +107,7 @@ module VagrantPlugins
b2.use ConfigValidate
b2.use action_halt
b2.use EnvSet, build_rebuild: true
b2.use action_start
end
end
@ -202,18 +203,18 @@ module VagrantPlugins
end
end
# We only want to actually sync folder differences if
# we're not created.
b2.use Call, IsState, :not_created do |env2, b3|
if !env2[:result]
b3.use EnvSet, host_machine_sync_folders: false
end
end
b2.use HostMachineBuildDir
b2.use HostMachineSyncFolders
b2.use PrepareNFSValidIds
b2.use SyncedFolderCleanup
b2.use PrepareNFSSettings
b2.use Build
# If the VM is NOT created yet, then do some setup steps
# necessary for creating it.
@ -245,11 +246,13 @@ module VagrantPlugins
# The autoload farm
action_root = Pathname.new(File.expand_path("../action", __FILE__))
autoload :Build, action_root.join("build")
autoload :CompareSyncedFolders, action_root.join("compare_synced_folders")
autoload :Create, action_root.join("create")
autoload :Destroy, action_root.join("destroy")
autoload :HasSSH, action_root.join("has_ssh")
autoload :HostMachine, action_root.join("host_machine")
autoload :HostMachineBuildDir, action_root.join("host_machine_build_dir")
autoload :HostMachinePortChecker, action_root.join("host_machine_port_checker")
autoload :HostMachinePortWarning, action_root.join("host_machine_port_warning")
autoload :HostMachineRequired, action_root.join("host_machine_required")

View File

@ -0,0 +1,51 @@
require "log4r"
module VagrantPlugins
module DockerProvider
module Action
class Build
def initialize(app, env)
@app = app
@logger = Log4r::Logger.new("vagrant::docker::build")
end
def call(env)
machine = env[:machine]
build_dir = env[:build_dir]
build_dir ||= machine.provider_config.build_dir
# If we're not building a container, then just skip this step
return @app.call(env) if !build_dir
# Try to read the image ID from the cache file if we've
# already built it.
image_file = machine.data_dir.join("docker_build_image")
image = nil
if image_file.file?
image = image_file.read.chomp
end
# If we have no image or we're rebuilding, we rebuild
if !image || env[:build_rebuild]
# Build it
machine.ui.output(I18n.t("docker_provider.building"))
image = machine.provider.driver.build(build_dir)
# Store the image ID
image_file.open("w") do |f|
f.binmode
f.write("#{image}\n")
end
else
machine.ui.output(I18n.t("docker_provider.already_built"))
end
# Set the image for creation
env[:create_image] = image
@app.call(env)
end
end
end
end
end

View File

@ -50,6 +50,9 @@ module VagrantPlugins
container_name << "_#{Time.now.to_i}"
end
image = @env[:create_image]
image ||= @provider_config.image
links = {}
@provider_config._links.each do |link|
parts = link.split(":", 2)
@ -61,7 +64,7 @@ module VagrantPlugins
env: @provider_config.env,
extra_args: @provider_config.create_args,
hostname: @machine_config.vm.hostname,
image: @provider_config.image,
image: image,
links: links,
name: container_name,
ports: forwarded_ports,

View File

@ -0,0 +1,46 @@
require "digest/md5"
require "log4r"
module VagrantPlugins
module DockerProvider
module Action
class HostMachineBuildDir
def initialize(app, env)
@app = app
@logger = Log4r::Logger.new("vagrant::docker::hostmachinebuilddir")
end
def call(env)
machine = env[:machine]
build_dir = machine.provider_config.build_dir
# If we're not building a Dockerfile, ignore
return @app.call(env) if !build_dir
# If we're building a docker file, expand the directory
build_dir = File.expand_path(build_dir, env[:machine].env.root_path)
env[:build_dir] = build_dir
# If we're not on a host VM, we're done
return @app.call(env) if !machine.provider.host_vm?
# We're on a host VM, so we need to move our build dir to
# that machine. We do this by putting the synced folder on
# ourself and letting HostMachineSyncFolders handle it.
new_build_dir = "/mnt/docker_build_#{Digest::MD5.hexdigest(build_dir)}"
options = {
docker__ignore: true,
docker__exact: true,
}
machine.config.vm.synced_folder(build_dir, new_build_dir, options)
# Set the build dir to be the correct one
env[:build_dir] = new_build_dir
@app.call(env)
end
end
end
end
end

View File

@ -91,15 +91,23 @@ module VagrantPlugins
# Generate an ID that is deterministic based on our machine
# and Vagrantfile path...
id = Digest::MD5.hexdigest(
"#{env[:machine].env.root_path}#{env[:machine].name}")
"#{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[:guestpath] = "/mnt/docker_#{Time.now.to_i}_#{rand(100000)}"
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)

View File

@ -3,6 +3,12 @@ module VagrantPlugins
class Config < Vagrant.plugin("2", :config)
attr_accessor :image, :cmd, :ports, :volumes, :privileged
# The directory with a Dockerfile to build and use as the basis
# for this container. If this is set, "image" should not be set.
#
# @return [String]
attr_accessor :build_dir
# Additional arguments to pass to `docker run` when creating
# the container for the first time. This is an array of args.
#
@ -54,6 +60,7 @@ module VagrantPlugins
attr_accessor :vagrant_vagrantfile
def initialize
@build_dir = UNSET_VALUE
@cmd = UNSET_VALUE
@create_args = []
@env = {}
@ -87,6 +94,7 @@ module VagrantPlugins
end
def finalize!
@build_dir = nil if @build_dir == UNSET_VALUE
@cmd = [] if @cmd == UNSET_VALUE
@create_args = [] if @create_args == UNSET_VALUE
@env ||= {}

View File

@ -14,6 +14,17 @@ module VagrantPlugins
@executor = Executor::Local.new
end
def build(dir, **opts)
result = execute('docker', 'build', dir)
regexp = /Successfully built (.+)$/i
match = regexp.match(result)
if !match
# TODO: error
end
match[1]
end
def create(params)
image = params.fetch(:image)
links = params.fetch(:links)

View File

@ -16,9 +16,9 @@ module VagrantPlugins
end
def prepare(machine, folders, _opts)
# FIXME: Check whether the container has already been created with
# different synced folders and let the user know about it
folders.each do |id, data|
next if data[:ignore]
host_path = data[:hostpath]
guest_path = data[:guestpath]
machine.provider_config.volumes << "#{host_path}:#{guest_path}"

View File

@ -1,5 +1,9 @@
en:
docker_provider:
already_built: |-
Image is already built from the Dockerfile. `vagrant reload` to rebuild.
building: |-
Building the container from a Dockerfile...
creating: |-
Creating the container...
created: |-

View File

@ -24,6 +24,7 @@ describe VagrantPlugins::DockerProvider::Config do
describe "defaults" do
before { subject.finalize! }
its(:build_dir) { should be_nil }
its(:cmd) { should eq([]) }
its(:env) { should eq({}) }
its(:image) { should be_nil }

View File

@ -25,7 +25,8 @@ describe Vagrant::Util::ScopedHashOverride do
}
expected = {
:key => "replaced"
:key => "replaced",
:scope__key => "replaced"
}
expect(klass.scoped_hash_override(original, "scope")).to eq(expected)
@ -40,6 +41,7 @@ describe Vagrant::Util::ScopedHashOverride do
expected = {
:key => "replaced",
:scope__key => "replaced",
:another__key => "value"
}