From a05d95bd0ac7b988a450a4e57b3a8f7c0fcc2e6a Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Fri, 23 Jun 2017 16:32:36 -0700 Subject: [PATCH] (#7139) Add post-install provisioner to docker provisioner Prior to this commit, if a user attempted to configure `/etc/default/docker` through vagrant prior to installation, the package manager would not override an existing configuration and installing docker would then fail. This commit fixes this by introducing a `post_install_provisioner` that allows users to define a provisioner block that will run after docker has been installed, allowing users to configure `/etc/default/docker` how they want. --- plugins/provisioners/docker/config.rb | 11 +++ plugins/provisioners/docker/installer.rb | 6 +- plugins/provisioners/docker/provisioner.rb | 19 ++++- templates/locales/en.yml | 3 + .../provisioners/docker/config_test.rb | 22 ++++++ .../provisioners/docker/provisioner_test.rb | 79 +++++++++++++++++++ .../source/docs/provisioning/docker.html.md | 18 ++++- 7 files changed, 153 insertions(+), 5 deletions(-) create mode 100644 test/unit/plugins/provisioners/docker/provisioner_test.rb diff --git a/plugins/provisioners/docker/config.rb b/plugins/provisioners/docker/config.rb index 44409a41d..66f651633 100644 --- a/plugins/provisioners/docker/config.rb +++ b/plugins/provisioners/docker/config.rb @@ -4,9 +4,11 @@ module VagrantPlugins module DockerProvisioner class Config < Vagrant.plugin("2", :config) attr_reader :images + attr_accessor :post_install_provisioner def initialize @images = Set.new + @post_install_provisioner = nil @__build_images = [] @__containers = Hash.new { |h, k| h[k] = {} } @@ -38,6 +40,15 @@ module VagrantPlugins @images += images.map(&:to_s) end + def post_install_provision(name, **options, &block) + # Abort + raise DockerError, :wrong_provisioner if options[:type] == "docker" + + proxy = VagrantPlugins::Kernel_V2::VMConfig.new + proxy.provision(name, **options, &block) + @post_install_provisioner = proxy.provisioners.first + end + def run(name, **options) @__containers[name.to_s] = options.dup end diff --git a/plugins/provisioners/docker/installer.rb b/plugins/provisioners/docker/installer.rb index 3f2ae95ad..9746cd5f5 100644 --- a/plugins/provisioners/docker/installer.rb +++ b/plugins/provisioners/docker/installer.rb @@ -8,10 +8,12 @@ module VagrantPlugins # This handles verifying the Docker installation, installing it if it was # requested, and so on. This method will raise exceptions if things are # wrong. + # @return [Boolean] - false if docker cannot be detected on machine, else + # true if docker installs correctly or is installed def ensure_installed if !@machine.guest.capability?(:docker_installed) @machine.ui.warn(I18n.t("vagrant.docker_cant_detect")) - return + return false end if !@machine.guest.capability(:docker_installed) @@ -26,6 +28,8 @@ module VagrantPlugins if @machine.guest.capability?(:docker_configure_vagrant_user) @machine.guest.capability(:docker_configure_vagrant_user) end + + true end end end diff --git a/plugins/provisioners/docker/provisioner.rb b/plugins/provisioners/docker/provisioner.rb index aa4aca27e..3873030ff 100644 --- a/plugins/provisioners/docker/provisioner.rb +++ b/plugins/provisioners/docker/provisioner.rb @@ -19,7 +19,16 @@ module VagrantPlugins @logger = Log4r::Logger.new("vagrant::provisioners::docker") @logger.info("Checking for Docker installation...") - @installer.ensure_installed + if @installer.ensure_installed + if !config.post_install_provisioner.nil? + @logger.info("Running post setup provision script...") + env = { + callable: method(:run_provisioner), + provisioner: config.post_install_provisioner, + machine: machine} + machine.env.hook(:run_provisioner, env) + end + end # Attempt to start service if not running @client.start_service @@ -40,6 +49,14 @@ module VagrantPlugins @client.run(config.containers) end end + + def run_provisioner(env) + klass = Vagrant.plugin("2").manager.provisioners[env[:provisioner].type] + result = klass.new(env[:machine], env[:provisioner].config) + result.config.finalize! + + result.provision + end end end end diff --git a/templates/locales/en.yml b/templates/locales/en.yml index af3fad1a4..4c81d3ab3 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -2361,6 +2361,9 @@ en: Please check https://docs.ansible.com/intro_installation.html#control-machine-requirements docker: + wrong_provisioner: |- + The Docker post-install provisioner cannot also take a Docker post-install + provisioner not_running: "Docker is not running on the guest VM." install_failed: "Docker installation failed." diff --git a/test/unit/plugins/provisioners/docker/config_test.rb b/test/unit/plugins/provisioners/docker/config_test.rb index 3f3bfb15a..a0952bb60 100644 --- a/test/unit/plugins/provisioners/docker/config_test.rb +++ b/test/unit/plugins/provisioners/docker/config_test.rb @@ -1,6 +1,7 @@ require File.expand_path("../../../../base", __FILE__) require Vagrant.source_root.join("plugins/provisioners/docker/config") +require Vagrant.source_root.join("plugins/kernel_v2/config/vm") describe VagrantPlugins::DockerProvisioner::Config do subject { described_class.new } @@ -137,4 +138,25 @@ describe VagrantPlugins::DockerProvisioner::Config do }) end end + + describe "#post_install_provision" do + it "raises an error if 'docker' provisioner was provided" do + expect {subject.post_install_provision("myprov", :type=>"docker", :inline=>"echo 'hello'")} + .to raise_error() + end + + it "setups a basic provisioner" do + prov = double() + mock_provisioner = "mock" + mock_provisioners = [mock_provisioner] + + allow(VagrantPlugins::Kernel_V2::VMConfig).to receive(:new). + and_return(prov) + allow(prov).to receive(:provision).and_return(mock_provisioners) + allow(prov).to receive(:provisioners).and_return(mock_provisioners) + + subject.post_install_provision("myprov", :inline=>"echo 'hello'") + expect(subject.post_install_provisioner).to eq(mock_provisioner) + end + end end diff --git a/test/unit/plugins/provisioners/docker/provisioner_test.rb b/test/unit/plugins/provisioners/docker/provisioner_test.rb new file mode 100644 index 000000000..29890dac6 --- /dev/null +++ b/test/unit/plugins/provisioners/docker/provisioner_test.rb @@ -0,0 +1,79 @@ +require File.expand_path("../../../../base", __FILE__) + +require Vagrant.source_root.join("plugins/provisioners/docker/provisioner") + +describe VagrantPlugins::DockerProvisioner::Provisioner do + include_context "unit" + subject { described_class.new(machine, config, installer, client) } + + let(:iso_env) do + # We have to create a Vagrantfile so there is a root path + env = isolated_environment + env.vagrantfile("") + env.create_vagrant_env + end + + let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) } + let(:config) { double("config") } + let(:communicator) { double("comm") } + let(:guest) { double("guest") } + let(:client) { double("client") } + let(:installer) { double("installer") } + let(:hook) { double("hook") } + + before do + machine.stub(communicate: communicator) + machine.stub(guest: guest) + + communicator.stub(execute: true) + communicator.stub(upload: true) + + guest.stub(capability?: false) + guest.stub(capability: false) + + client.stub(start_service: true) + client.stub(daemon_running?: true) + + config.stub(images: Set.new) + config.stub(build_images: Set.new) + config.stub(containers: Hash.new) + end + + describe "#provision" do + let(:provisioner) do + prov = VagrantPlugins::Kernel_V2::VagrantConfigProvisioner.new("spec-test", :shell) + prov.config = {} + prov + end + + it "invokes a post_install_provisioner if defined and docker is installed" do + installer.stub(ensure_installed: true) + allow(config).to receive(:post_install_provisioner).and_return(provisioner) + allow(machine).to receive(:env).and_return(iso_env) + allow(machine.env).to receive(:hook).and_return(true) + + expect(machine.env).to receive(:hook).with(:run_provisioner, anything) + subject.provision() + end + + it "does not invoke post_install_provisioner if not defined" do + installer.stub(ensure_installed: true) + allow(config).to receive(:post_install_provisioner).and_return(nil) + allow(machine).to receive(:env).and_return(iso_env) + allow(machine.env).to receive(:hook).and_return(true) + + expect(machine.env).not_to receive(:hook).with(:run_provisioner, anything) + subject.provision() + end + + it "raises an error if docker daemon isn't running" do + allow(installer).to receive(:ensure_installed).and_return(false) + allow(client).to receive(:start_service).and_return(false) + allow(client).to receive(:daemon_running?).and_return(false) + + expect { subject.provision() }. + to raise_error(VagrantPlugins::DockerProvisioner::DockerError) + end + end + +end diff --git a/website/source/docs/provisioning/docker.html.md b/website/source/docs/provisioning/docker.html.md index 469db2096..ff0ab6337 100644 --- a/website/source/docs/provisioning/docker.html.md +++ b/website/source/docs/provisioning/docker.html.md @@ -53,6 +53,9 @@ of these functions have examples in more detailed sections below. * `pull_images` - Pull the given images. This does not start these images. +* `post_install_provisioner` - A [provisioner block](/docs/provisioning) that runs post docker + installation. + * `run` - Run a container and configure it to start on boot. This can only be specified once. @@ -191,6 +194,15 @@ that are generally useful to know if you are using this provisioner. ### Customize `/etc/default/docker` -To customize this file, use a shell provisioner before the Docker provisioner -that sets this file up. The Docker provisioner will not modify this file -in a destructive way. +To customize this file, use the `post_install_provisioner` shell provisioner. + +```ruby +Vagrant.configure("2") do |config| + config.vm.provision "docker" do |d| + d.post_install_provision "shell", inline:"echo export http_proxy='http://127.0.0.1:3128/' >> /etc/default/docker" + d.run "ubuntu", + cmd: "bash -l", + args: "-v '/vagrant:/var/www'" + end +end +```