diff --git a/plugins/provisioners/docker/cap/debian/docker_configure_auto_start.rb b/plugins/provisioners/docker/cap/debian/docker_configure_auto_start.rb new file mode 100644 index 000000000..52c438f20 --- /dev/null +++ b/plugins/provisioners/docker/cap/debian/docker_configure_auto_start.rb @@ -0,0 +1,15 @@ +module VagrantPlugins + module Docker + module Cap + module Debian + module DockerConfigureAutoStart + def self.docker_configure_auto_start(machine) + if ! machine.communicate.test('grep -q \'\-r=true\' /etc/init/docker.conf') + machine.communicate.sudo("sed -i.bak 's/docker -d/docker -d -r=true/' /etc/init/docker.conf ") + end + end + end + end + end + end +end diff --git a/plugins/provisioners/docker/cap/debian/docker_configure_vagrant_user.rb b/plugins/provisioners/docker/cap/debian/docker_configure_vagrant_user.rb new file mode 100644 index 000000000..1136ddd79 --- /dev/null +++ b/plugins/provisioners/docker/cap/debian/docker_configure_vagrant_user.rb @@ -0,0 +1,13 @@ +module VagrantPlugins + module Docker + module Cap + module Debian + module DockerConfigureVagrantUser + def self.docker_configure_vagrant_user(machine) + machine.communicate.sudo("usermod -a -G docker #{machine.config.ssh.username || "vagrant"}") + end + end + end + end + end +end diff --git a/plugins/provisioners/docker/cap/debian/docker_install.rb b/plugins/provisioners/docker/cap/debian/docker_install.rb new file mode 100644 index 000000000..8d5773465 --- /dev/null +++ b/plugins/provisioners/docker/cap/debian/docker_install.rb @@ -0,0 +1,26 @@ +module VagrantPlugins + module Docker + module Cap + module Debian + module DockerInstall + def self.docker_install(machine, version) + package = 'lxc-docker' + package << "-#{version}" if version != :latest + + machine.communicate.tap do |comm| + # TODO: Perform check on the host machine if aufs is installed and using LXC + if machine.provider_name != :lxc + comm.sudo("lsmod | grep aufs || modprobe aufs || apt-get install -y linux-image-extra-`uname -r`") + end + comm.sudo("apt-get install -y --force-yes -q curl") + comm.sudo("curl http://get.docker.io/gpg | apt-key add -") + comm.sudo("echo deb http://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list") + comm.sudo("apt-get update") + comm.sudo("apt-get install -y --force-yes -q xz-utils #{package} -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold'") + end + end + end + end + end + end +end diff --git a/plugins/provisioners/docker/cap/debian/docker_start_service.rb b/plugins/provisioners/docker/cap/debian/docker_start_service.rb new file mode 100644 index 000000000..a97bade8b --- /dev/null +++ b/plugins/provisioners/docker/cap/debian/docker_start_service.rb @@ -0,0 +1,13 @@ +module VagrantPlugins + module Docker + module Cap + module Debian + module DockerStartService + def self.docker_start_service(machine) + machine.communicate.sudo("service docker start") + end + end + end + end + end +end diff --git a/plugins/provisioners/docker/cap/linux/docker_installed.rb b/plugins/provisioners/docker/cap/linux/docker_installed.rb new file mode 100644 index 000000000..f5d693501 --- /dev/null +++ b/plugins/provisioners/docker/cap/linux/docker_installed.rb @@ -0,0 +1,13 @@ +module VagrantPlugins + module Docker + module Cap + module Linux + module DockerInstalled + def self.docker_installed(machine) + machine.communicate.test("test -f /usr/bin/docker", sudo: true) + end + end + end + end + end +end diff --git a/plugins/provisioners/docker/config.rb b/plugins/provisioners/docker/config.rb new file mode 100644 index 000000000..3845cab2f --- /dev/null +++ b/plugins/provisioners/docker/config.rb @@ -0,0 +1,44 @@ +require 'set' + +module VagrantPlugins + module Docker + class Config < Vagrant.plugin("2", :config) + attr_reader :images, :containers + attr_accessor :version + + def initialize + @images = Set.new + @containers = Hash.new + @version = :latest + end + + def pull_images(*images) + @images += images.map(&:to_s) + end + + def run(*args) + container_name = args.shift + params = {} + + if args.empty? + params[:image] = container_name + elsif args.first.is_a?(String) + params[:image] = args.shift + params[:cmd] = container_name + else + params = args.shift + params[:cmd] ||= container_name + end + + # TODO: Validate provided parameters before assignment + @containers[container_name.to_s] = params + end + + def merge(other) + super.tap do |result| + result.pull_images *(other.images + self.images) + end + end + end + end +end diff --git a/plugins/provisioners/docker/docker_client.rb b/plugins/provisioners/docker/docker_client.rb new file mode 100644 index 000000000..923a7dc9a --- /dev/null +++ b/plugins/provisioners/docker/docker_client.rb @@ -0,0 +1,102 @@ +require 'digest/sha1' + +module VagrantPlugins + module Docker + class DockerClient + def initialize(machine) + @machine = machine + end + + def pull_images(*images) + @machine.communicate.tap do |comm| + images.each do |image| + comm.sudo("docker images | grep -q #{image} || docker pull #{image}") + end + end + end + + def start_service + if !daemon_running? && @machine.guest.capability?(:docker_start_service) + @machine.guest.capability(:docker_start_service) + end + end + + def daemon_running? + @machine.communicate.test('test -f /var/run/docker.pid') + end + + def run(containers) + containers.each do |name, config| + cids_dir = "/var/lib/vocker/cids" + config[:cidfile] ||= "#{cids_dir}/#{Digest::SHA1.hexdigest name}" + + @machine.communicate.sudo("mkdir -p #{cids_dir}") + run_container({name: name}.merge config) + end + end + + def run_container(config) + raise "Container's cidfile was not provided!" unless config[:cidfile] + + id = "$(cat #{config[:cidfile]})" + + if container_exist?(id) + start_container(id) + else + create_container(config) + end + end + + def container_exist?(id) + @machine.communicate.test("sudo docker ps -a -q | grep -q #{id}") + end + + def start_container(id) + unless container_running?(id) + @machine.communicate.sudo("docker start #{id}") + end + end + + def container_running?(id) + @machine.communicate.test("sudo docker ps -q | grep #{id}") + end + + def create_container(config) + # DISCUSS: Does this really belong here? + ensure_bind_mounts_exist(config) + + args = "-cidfile=#{config[:cidfile]} -d " + args << prepare_run_arguments(config) + @machine.communicate.sudo %[ + rm -f #{config[:cidfile]} + docker run #{args} #{config[:image]} #{config[:cmd]} + ] + end + + private + + def ensure_bind_mounts_exist(config) + Array(config[:volumes]).each do |volume| + if volume =~ /(.+):.+/ + guest_vm_path = $1 + @machine.communicate.sudo "mkdir -p #{guest_vm_path}" + end + end + end + + def prepare_run_arguments(config) + args = [] + + args << "-dns=#{config[:dns]}" if config[:dns] + args << "-name=#{config[:name]}" if config[:name] + args << "#{config[:additional_run_args]}" if config[:additional_run_args] + + args += Array(config[:volumes]).map { |volume| "-v #{volume}" } + args += Array(config[:ports]).map { |port| "-p #{port}" } + args += Array(config[:links]).map { |link| "-link #{link}" } + + args.compact.flatten.join ' ' + end + end + end +end diff --git a/plugins/provisioners/docker/docker_installer.rb b/plugins/provisioners/docker/docker_installer.rb new file mode 100644 index 000000000..068bf6799 --- /dev/null +++ b/plugins/provisioners/docker/docker_installer.rb @@ -0,0 +1,41 @@ +require_relative "errors" + +module VagrantPlugins + module Docker + class DockerInstaller + def initialize(machine, version) + @machine = machine + @version = version + end + + # This handles verifying the Docker installation, installing it if it was + # requested, and so on. This method will raise exceptions if things are + # wrong. + def ensure_installed + if !@machine.guest.capability?(:docker_installed) + @machine.ui.warn(I18n.t("vagrant.docker_cant_detect")) + return + end + + if !@machine.guest.capability(:docker_installed) + @machine.ui.info(I18n.t("vagrant.docker_installing")) + @machine.guest.capability(:docker_install, @version) + + if !@machine.guest.capability(:docker_installed) + raise Errors::DockerInstallFailed + end + end + + if @machine.guest.capability?(:docker_configure_auto_start) + @machine.guest.capability(:docker_configure_auto_start) + else + @machine.env.ui.warn I18n.t('vagrant.docker_auto_start_not_available') + end + + if @machine.guest.capability?(:docker_configure_vagrant_user) + @machine.guest.capability(:docker_configure_vagrant_user) + end + end + end + end +end diff --git a/plugins/provisioners/docker/errors.rb b/plugins/provisioners/docker/errors.rb new file mode 100644 index 000000000..4866d3a40 --- /dev/null +++ b/plugins/provisioners/docker/errors.rb @@ -0,0 +1,11 @@ +module VagrantPlugins + module Docker + module Errors + class DockerInstallFailed < ::Vagrant::Errors::VagrantError + end + + class DockerNotRunning < ::Vagrant::Errors::VagrantError + end + end + end +end diff --git a/plugins/provisioners/docker/plugin.rb b/plugins/provisioners/docker/plugin.rb new file mode 100644 index 000000000..72bc8463d --- /dev/null +++ b/plugins/provisioners/docker/plugin.rb @@ -0,0 +1,48 @@ +require "vagrant" + +module VagrantPlugins + module Docker + class Plugin < Vagrant.plugin("2") + name "docker" + description <<-DESC + Provides support for provisioning your virtual machines with + Docker images and containers. + DESC + + config(:docker, :provisioner) do + require_relative "config" + Config + end + + guest_capability("debian", "docker_install") do + require_relative "cap/debian/docker_install" + Cap::Debian::DockerInstall + end + + guest_capability("debian", "docker_configure_auto_start") do + require_relative "cap/debian/docker_configure_auto_start" + Cap::Debian::DockerConfigureAutoStart + end + + guest_capability("debian", "docker_configure_vagrant_user") do + require_relative "cap/debian/docker_configure_vagrant_user" + Cap::Debian::DockerConfigureVagrantUser + end + + guest_capability("debian", "docker_start_service") do + require_relative "cap/debian/docker_start_service" + Cap::Debian::DockerStartService + end + + guest_capability("linux", "docker_installed") do + require_relative "cap/linux/docker_installed" + Cap::Linux::DockerInstalled + end + + provisioner(:docker) do + require_relative "provisioner" + Provisioner + end + end + end +end diff --git a/plugins/provisioners/docker/provisioner.rb b/plugins/provisioners/docker/provisioner.rb new file mode 100644 index 000000000..d1da07714 --- /dev/null +++ b/plugins/provisioners/docker/provisioner.rb @@ -0,0 +1,41 @@ +require_relative "errors" +require_relative "docker_client" +require_relative "docker_installer" + +module VagrantPlugins + module Docker + # TODO: Improve handling of vagrant-lxc specifics (like checking for apparmor + # profile stuff + autocorrection) + class Provisioner < Vagrant.plugin("2", :provisioner) + def initialize(machine, config, installer = nil, client = nil) + super(machine, config) + # TODO: Rename to installer / client (drop docker suffix) + @installer = installer || DockerInstaller.new(@machine, config.version) + @client = client || DockerClient.new(@machine) + end + + def provision + @logger = Log4r::Logger.new("vagrant::provisioners::vocker") + + @logger.info("Checking for Docker installation...") + @installer.ensure_installed + + # Attempt to start service if not running + @client.start_service + unless @client.daemon_running? + raise Errors::DockerNotRunning + end + + if config.images.any? + @machine.ui.info(I18n.t("vagrant.docker_pulling_images")) + @client.pull_images(*config.images) + end + + if config.containers.any? + @machine.ui.info(I18n.t("vagrant.docker_starting_containers")) + @client.run(config.containers) + end + end + end + end +end