diff --git a/lib/vagrant/util/scoped_hash_override.rb b/lib/vagrant/util/scoped_hash_override.rb index ec4eb180d..ebe1e4959 100644 --- a/lib/vagrant/util/scoped_hash_override.rb +++ b/lib/vagrant/util/scoped_hash_override.rb @@ -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 diff --git a/plugins/providers/docker/action.rb b/plugins/providers/docker/action.rb index bb6dc514a..b8b61f48d 100644 --- a/plugins/providers/docker/action.rb +++ b/plugins/providers/docker/action.rb @@ -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") diff --git a/plugins/providers/docker/action/build.rb b/plugins/providers/docker/action/build.rb new file mode 100644 index 000000000..436c775b2 --- /dev/null +++ b/plugins/providers/docker/action/build.rb @@ -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 diff --git a/plugins/providers/docker/action/create.rb b/plugins/providers/docker/action/create.rb index c25223be6..0675cb770 100644 --- a/plugins/providers/docker/action/create.rb +++ b/plugins/providers/docker/action/create.rb @@ -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, diff --git a/plugins/providers/docker/action/host_machine_build_dir.rb b/plugins/providers/docker/action/host_machine_build_dir.rb new file mode 100644 index 000000000..3b926d849 --- /dev/null +++ b/plugins/providers/docker/action/host_machine_build_dir.rb @@ -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 diff --git a/plugins/providers/docker/action/host_machine_sync_folders.rb b/plugins/providers/docker/action/host_machine_sync_folders.rb index 800baebfe..7fa25ac3e 100644 --- a/plugins/providers/docker/action/host_machine_sync_folders.rb +++ b/plugins/providers/docker/action/host_machine_sync_folders.rb @@ -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) diff --git a/plugins/providers/docker/config.rb b/plugins/providers/docker/config.rb index 2f1476832..6541d4a6e 100644 --- a/plugins/providers/docker/config.rb +++ b/plugins/providers/docker/config.rb @@ -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 ||= {} diff --git a/plugins/providers/docker/driver.rb b/plugins/providers/docker/driver.rb index 116db4700..8ef26d1dd 100644 --- a/plugins/providers/docker/driver.rb +++ b/plugins/providers/docker/driver.rb @@ -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) diff --git a/plugins/providers/docker/synced_folder.rb b/plugins/providers/docker/synced_folder.rb index 248502b1b..e18682586 100644 --- a/plugins/providers/docker/synced_folder.rb +++ b/plugins/providers/docker/synced_folder.rb @@ -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}" diff --git a/templates/locales/providers_docker.yml b/templates/locales/providers_docker.yml index 255ec44e0..966029001 100644 --- a/templates/locales/providers_docker.yml +++ b/templates/locales/providers_docker.yml @@ -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: |- diff --git a/test/unit/plugins/providers/docker/config_spec.rb b/test/unit/plugins/providers/docker/config_spec.rb index 3c71f8144..8f2e473b6 100644 --- a/test/unit/plugins/providers/docker/config_spec.rb +++ b/test/unit/plugins/providers/docker/config_spec.rb @@ -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 } diff --git a/test/unit/vagrant/util/scoped_hash_override_test.rb b/test/unit/vagrant/util/scoped_hash_override_test.rb index 246e187e3..0bc6ced11 100644 --- a/test/unit/vagrant/util/scoped_hash_override_test.rb +++ b/test/unit/vagrant/util/scoped_hash_override_test.rb @@ -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" }