├── .gitignore ├── .rspec ├── .travis.yml ├── CHANGELOG.md ├── Gemfile ├── Gemfile.lock ├── Guardfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── boxes ├── dind │ ├── .gitignore │ ├── Dockerfile │ ├── README.md │ ├── Vagrantfile.sample │ └── metadata.json ├── dummy │ ├── README.md │ └── metadata.json └── precise │ ├── .gitignore │ ├── Dockerfile │ ├── README.md │ ├── Vagrantfile.sample │ └── metadata.json ├── development └── Vagrantfile ├── docker-provider.gemspec ├── example ├── Vagrantfile └── site.pp ├── lib ├── docker-provider.rb └── docker-provider │ ├── action.rb │ ├── action │ ├── check_running.rb │ ├── create.rb │ ├── created.rb │ ├── destroy.rb │ ├── forward_ports.rb │ ├── is_running.rb │ ├── message.rb │ ├── prepare_nfs_settings.rb │ ├── prepare_nfs_valid_ids.rb │ ├── start.rb │ └── stop.rb │ ├── config.rb │ ├── driver.rb │ ├── errors.rb │ ├── plugin.rb │ ├── provider.rb │ ├── synced_folder.rb │ └── version.rb ├── locales └── en.yml ├── spec ├── acceptance │ ├── provider │ │ ├── basic_spec.rb │ │ ├── network_forwarded_port_spec.rb │ │ └── synced_folder_spec.rb │ ├── provisioner │ │ ├── chef_solo_spec.rb │ │ ├── puppet_spec.rb │ │ └── shell_spec.rb │ └── synced_folder │ │ └── nfs_spec.rb ├── spec_helper.rb ├── support │ └── unit_example_group.rb └── unit │ └── driver_spec.rb └── vagrant-spec.config.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | InstalledFiles 7 | _yardoc 8 | coverage 9 | doc/ 10 | lib/bundler/man 11 | pkg 12 | rdoc 13 | spec/reports 14 | test/tmp 15 | test/version_tmp 16 | tmp 17 | /development/.vagrant 18 | /example/.vagrant 19 | /spec/acceptance/.vagrant 20 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format progress 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 1.9.3 4 | - 2.0.0 5 | matrix: 6 | allow_failures: 7 | - rvm: 2.0.0 8 | script: "bundle exec rake spec:unit" 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.1.0](https://github.com/fgrehm/docker-provider/compare/v0.0.2...0.1.0) (Feb 27, 2014) 2 | 3 | BACKWARDS INCOMPATIBILITY: 4 | 5 | - Support for Vagrant < 1.4 and Docker < 0.7.0 are gone, please use a previous 6 | plugin version if you can't upgrade. 7 | 8 | FEATURES: 9 | 10 | - Trusted build for official base boxes Docker images [GH-5] 11 | - New base boxes / images for running Ubuntu 12.04 in "machine mode" 12 | - New [dind](https://github.com/jpetazzo/dind) Ubuntu 12.04 base box 13 | - Support for NFS Synced Folders 14 | - Support for running privileged containers 15 | 16 | IMPROVEMENTS: 17 | 18 | - Decrease timeout taken before killing a container on `vagrant halt`s 19 | - Remove volumes along with containers with `docker rm -v` on `vagrant destroy`s 20 | - Sanity checks using [vagrant-spec](https://github.com/mitchellh/vagrant-spec) 21 | 22 | BUG FIXES: 23 | 24 | - Fix `vagrant up` failure with newer Vagrant and Docker versions. 25 | 26 | ## [0.0.2](https://github.com/fgrehm/docker-provider/compare/v0.0.1...v0.0.2) (November 5, 2013) 27 | 28 | - Fix provisioning with Vagrant's built in provisioners 29 | 30 | ## 0.0.1 (November 5, 2013) 31 | 32 | - Initial public release. 33 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in docker-provider.gemspec 4 | gemspec 5 | 6 | group :development, :test do 7 | gem 'vagrant', github: 'mitchellh/vagrant', tag: 'v1.4.3' 8 | gem 'vagrant-spec', github: 'mitchellh/vagrant-spec', ref: 'fbd067bbe5e2a789bb2b29c38d1224cdd9386836' 9 | end 10 | 11 | group :development do 12 | gem 'guard' 13 | gem 'guard-rspec' 14 | gem 'rb-inotify' 15 | 16 | gem 'vagrant-notify', github: 'fgrehm/vagrant-notify' 17 | gem 'vagrant-cachier', github: 'fgrehm/vagrant-cachier' 18 | gem 'vagrant-pristine', github: 'fgrehm/vagrant-pristine' 19 | gem 'vagrant-lxc', github: 'fgrehm/vagrant-lxc' 20 | gem 'ventriloquist', github: 'fgrehm/ventriloquist' 21 | end 22 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: git://github.com/fgrehm/vagrant-cachier.git 3 | revision: 2faa6615466f8d518893f5ba51b493b877d2efde 4 | specs: 5 | vagrant-cachier (0.6.1.dev) 6 | 7 | GIT 8 | remote: git://github.com/fgrehm/vagrant-lxc.git 9 | revision: 4a84d95ff26bf2d887b127765a9ae404899734f9 10 | specs: 11 | vagrant-lxc (0.8.1.dev) 12 | 13 | GIT 14 | remote: git://github.com/fgrehm/vagrant-notify.git 15 | revision: 56cc81882a6ca0c74ad662c81fca8105a307782c 16 | specs: 17 | vagrant-notify (0.4.1.dev) 18 | 19 | GIT 20 | remote: git://github.com/fgrehm/vagrant-pristine.git 21 | revision: 503dbc47848c81d0fbfa6840491856f518d244a1 22 | specs: 23 | vagrant-pristine (0.3.0) 24 | 25 | GIT 26 | remote: git://github.com/fgrehm/ventriloquist.git 27 | revision: 8f87f95e69f5dba6faf1b9b8c74ad51a0068d8b7 28 | specs: 29 | ventriloquist (0.4.2.dev) 30 | vocker (~> 0.4.1) 31 | 32 | GIT 33 | remote: git://github.com/mitchellh/vagrant-spec.git 34 | revision: fbd067bbe5e2a789bb2b29c38d1224cdd9386836 35 | ref: fbd067bbe5e2a789bb2b29c38d1224cdd9386836 36 | specs: 37 | vagrant-spec (0.0.1) 38 | childprocess (~> 0.3.7) 39 | log4r (~> 1.1.9) 40 | rspec (~> 2.14) 41 | thor (~> 0.18.1) 42 | 43 | GIT 44 | remote: git://github.com/mitchellh/vagrant.git 45 | revision: 4f0eb9504cc786d5a57a43814427e8eb35407a4c 46 | tag: v1.4.3 47 | specs: 48 | vagrant (1.4.3) 49 | childprocess (~> 0.3.7) 50 | erubis (~> 2.7.0) 51 | i18n (~> 0.6.0) 52 | log4r (~> 1.1.9) 53 | net-scp (~> 1.1.0) 54 | net-ssh (>= 2.6.6, < 2.8.0) 55 | 56 | PATH 57 | remote: . 58 | specs: 59 | docker-provider (0.1.0) 60 | 61 | GEM 62 | remote: https://rubygems.org/ 63 | specs: 64 | celluloid (0.15.2) 65 | timers (~> 1.1.0) 66 | celluloid-io (0.15.0) 67 | celluloid (>= 0.15.0) 68 | nio4r (>= 0.5.0) 69 | childprocess (0.3.9) 70 | ffi (~> 1.0, >= 1.0.11) 71 | coderay (1.1.0) 72 | diff-lcs (1.2.5) 73 | erubis (2.7.0) 74 | ffi (1.9.3) 75 | formatador (0.2.4) 76 | guard (2.5.1) 77 | formatador (>= 0.2.4) 78 | listen (~> 2.6) 79 | lumberjack (~> 1.0) 80 | pry (>= 0.9.12) 81 | thor (>= 0.18.1) 82 | guard-rspec (4.2.7) 83 | guard (~> 2.1) 84 | rspec (>= 2.14, < 4.0) 85 | i18n (0.6.9) 86 | listen (2.6.1) 87 | celluloid (>= 0.15.2) 88 | celluloid-io (>= 0.15.0) 89 | rb-fsevent (>= 0.9.3) 90 | rb-inotify (>= 0.9) 91 | log4r (1.1.10) 92 | lumberjack (1.0.4) 93 | method_source (0.8.2) 94 | net-scp (1.1.2) 95 | net-ssh (>= 2.6.5) 96 | net-ssh (2.7.0) 97 | nio4r (1.0.0) 98 | pry (0.9.12.6) 99 | coderay (~> 1.0) 100 | method_source (~> 0.8) 101 | slop (~> 3.4) 102 | rake (10.1.1) 103 | rb-fsevent (0.9.4) 104 | rb-inotify (0.9.3) 105 | ffi (>= 0.5.0) 106 | rspec (2.14.1) 107 | rspec-core (~> 2.14.0) 108 | rspec-expectations (~> 2.14.0) 109 | rspec-mocks (~> 2.14.0) 110 | rspec-core (2.14.7) 111 | rspec-expectations (2.14.5) 112 | diff-lcs (>= 1.1.3, < 2.0) 113 | rspec-mocks (2.14.6) 114 | slop (3.4.7) 115 | thor (0.18.1) 116 | timers (1.1.0) 117 | vocker (0.4.1) 118 | 119 | PLATFORMS 120 | ruby 121 | 122 | DEPENDENCIES 123 | bundler (~> 1.3) 124 | docker-provider! 125 | guard 126 | guard-rspec 127 | rake 128 | rb-inotify 129 | rspec 130 | vagrant! 131 | vagrant-cachier! 132 | vagrant-lxc! 133 | vagrant-notify! 134 | vagrant-pristine! 135 | vagrant-spec! 136 | ventriloquist! 137 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | guard 'rspec', :spec_paths => ["spec/unit"] do 2 | watch(%r{^spec/unit/.+_spec\.rb$}) 3 | watch(%r{^lib/docker-provider/(.+)\.rb$}) { |m| "spec/unit/#{m[1]}_spec.rb" } 4 | watch('spec/spec_helper.rb') { "spec/unit" } 5 | watch(%r{^spec/support/(.+)\.rb$}) { "spec/unit" } 6 | end 7 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2014 Fabio Rehm 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # docker-provider 2 | 3 | [![Build Status](https://travis-ci.org/fgrehm/docker-provider.png?branch=master)](https://travis-ci.org/fgrehm/docker-provider) [![Gem Version](https://badge.fury.io/rb/docker-provider.png)](http://badge.fury.io/rb/docker-provider) [![Gittip](http://img.shields.io/gittip/fgrehm.svg)](https://www.gittip.com/fgrehm/) 4 | 5 | A [Docker](http://www.docker.io/) provider for [Vagrant](http://www.vagrantup.com/) 6 | 1.4+. 7 | 8 | **NOTICE: This plugin is no longer being maintained as its functionality [has been merged back to Vagrant core](https://github.com/mitchellh/vagrant/pull/3347) and will be available with Vagrant 1.6+.** 9 | 10 | 11 | ## Warning 12 | 13 | This is experimental, expect things to break. 14 | 15 | 16 | ## Requirements 17 | 18 | * Vagrant 1.4+ 19 | * Docker 0.7.0+ 20 | 21 | 22 | ## Features 23 | 24 | * Support for Vagrant's `up`, `destroy`, `halt`, `reload` and `ssh` commands 25 | * Port forwarding 26 | * Synced / shared folders support 27 | * Set container hostnames from Vagrantfiles 28 | * Provision Docker containers with any built-in Vagrant provisioner (as long as the container has a SSH server running) 29 | 30 | You can see the plugin in action by watching the following asciicasts I published 31 | prior to releasing 0.0.1: 32 | 33 | * http://asciinema.org/a/6162 34 | * http://asciinema.org/a/6177 35 | 36 | 37 | ## Getting started 38 | 39 | If you are on a Mac / Windows machine, please fire up a x64 Linux VM with Docker + 40 | Vagrant 1.4+ installed or use [this Vagrantfile](https://gist.github.com/fgrehm/fc48fb51ec7df64439e4) 41 | and follow the instructions from within the VM. 42 | 43 | _It is likely that the plugin works with [boot2docker](http://boot2docker.github.io/) 44 | but I personally haven't tried that yet. If you are able to give it a go please 45 | [let me know](https://github.com/fgrehm/docker-provider/issues/new)._ 46 | 47 | ### Initial setup 48 | 49 | _If you are trying things out from a Vagrant VM using the `Vagrantfile` gisted 50 | above, you can skip to the next section_ 51 | 52 | The plugin requires Docker's executable to be available on current user's `PATH` 53 | and that the current user has been added to the `docker` group since we are not 54 | using `sudo` when interacting with Docker's CLI. For more information on setting 55 | this up please check [this page](http://docs.docker.io/en/latest/installation/ubuntulinux/#giving-non-root-access). 56 | 57 | ### `vagrant up` 58 | 59 | On its current state, the plugin is not "user friendly" and won't provide any kind 60 | of feedback about the process of downloading Docker images, so before you add a 61 | `docker-provider` [base box](http://docs.vagrantup.com/v2/boxes.html) it is recommended 62 | that you `docker pull` the associated base box images prior to spinning up `docker-provider` 63 | containers (otherwise you'll be staring at a blinking cursor without any progress 64 | information for a while). 65 | 66 | Assuming you have Vagrant 1.4+ and Docker 0.7.0+ installed just sing that same 67 | old song: 68 | 69 | ```sh 70 | vagrant plugin install docker-provider 71 | docker pull fgrehm/vagrant-ubuntu:precise 72 | vagrant box add precise64 http://bit.ly/vagrant-docker-precise 73 | vagrant init precise64 74 | vagrant up --provider=docker 75 | ``` 76 | 77 | Under the hood, that base box will [configure](#configuration) `docker-provider` 78 | to use the [`fgrehm/vagrant-ubuntu:precise`](https://index.docker.io/u/fgrehm/vagrant-ubuntu/) 79 | image that approximates a standard Vagrant box (`vagrant` user, default SSH key, 80 | etc.) and you should be good to go. 81 | 82 | 83 | ## Using custom images 84 | 85 | If you want to use a custom Docker image without creating a Vagrant base box, 86 | you can use a "dummy" box and configure things from your `Vagrantfile` like 87 | in [vagrant-digitalocean](https://github.com/smdahlen/vagrant-digitalocean#configure) 88 | or [vagrant-aws](https://github.com/mitchellh/vagrant-aws#quick-start): 89 | 90 | ```ruby 91 | Vagrant.configure("2") do |config| 92 | config.vm.box = "dummy" 93 | config.vm.box_url = "http://bit.ly/vagrant-docker-dummy" 94 | config.vm.provider :docker do |docker| 95 | docker.image = "your/image:tag" 96 | end 97 | end 98 | ``` 99 | 100 | 101 | ## Configuration 102 | 103 | This provider exposes a few provider-specific configuration options 104 | that are passed on to `docker run` under the hood when the container 105 | is being created: 106 | 107 | * `image` - Docker image to run (required) 108 | * `privileged` - Give extended privileges to the container (defaults to false) 109 | * `cmd` - An array of strings that makes up for the command to run the container (defaults to what has been set on your `Dockerfile` as `CMD` or `ENTRYPOINT`) 110 | * `ports` - An array of strings that makes up for the mapped network ports 111 | * `volumes` - An array of strings that makes up for the data volumes used by the container 112 | 113 | These can be set like typical provider-specific configuration: 114 | 115 | ```ruby 116 | Vagrant.configure("2") do |config| 117 | # ... other stuff 118 | 119 | config.vm.provider :docker do |docker| 120 | docker.image = 'fgrehm/vagrant-ubuntu-dind:precise' 121 | docker.privileged = true 122 | docker.cmd = ['/dind', '/sbin/init'] 123 | 124 | docker.ports << '1234:22' 125 | docker.volumes << '/var/lib/docker' 126 | end 127 | end 128 | ``` 129 | 130 | 131 | ## Networks 132 | 133 | Networking features in the form of `config.vm.network` are not supported with 134 | `docker-provider` apart from [forwarded ports](). 135 | If any of [`:private_network`](http://docs.vagrantup.com/v2/networking/private_network.html) 136 | or [`:public_network`](http://docs.vagrantup.com/v2/networking/public_network.html) 137 | are specified, Vagrant **won't** emit a warning. 138 | 139 | The same applies to changes on forwarded ports after the container has been 140 | created, Vagrant **won't** emit a warning to let you know that the ports specified 141 | on your `Vagrantfile` differs from what has been passed on to `docker run` when 142 | creating the container. 143 | 144 | _At some point the plugin will emit warnings on the scenarios described above, but 145 | not on its current state. Pull Requests are encouraged ;)_ 146 | 147 | 148 | ## Synced Folders 149 | 150 | There is support for synced folders on the form of [Docker volumes](http://docs.docker.io/en/latest/use/working_with_volumes/#mount-a-host-directory-as-a-container-volume) 151 | but as with forwarded ports, you won't be able to change them after the container 152 | has been created. [NFS](http://docs.vagrantup.com/v2/synced-folders/nfs.html) 153 | synced folders are also supported (as long as you set the `privileged` 154 | [config](#configuration) to true so that `docker-provider` can mount it on the 155 | guest container) and are capable of being reconfigured between `vagrant reload`s 156 | (different from Docker volumes). 157 | 158 | This is good enough for all built-in Vagrant provisioners (shell, 159 | chef, and puppet) to work! 160 | 161 | _At some point the plugin will emit warnings when the configured `Vagrantfile` 162 | synced folders / volumes differs from the ones used upon the container creation, 163 | but not on its current state. Pull Requests are encouraged ;)_ 164 | 165 | 166 | ## Box format 167 | 168 | Every provider in Vagrant must introduce a custom box format. This provider introduces 169 | `docker` boxes and you can view some examples in the [`boxes`](boxes) directory. 170 | That directory also contains instructions on how to build them. 171 | 172 | The box format is basically just the required `metadata.json` file along with a 173 | `Vagrantfile` that does default settings for the provider-specific configuration 174 | for this provider. 175 | 176 | 177 | ## Available base boxes 178 | 179 | | LINK | DESCRIPTION | 180 | | --- | --- | 181 | | http://bit.ly/vagrant-docker-precise | Ubuntu 12.04 Precise x86_64 with Puppet and Chef preinstalled and configured to run `/sbin/init` | 182 | | http://bit.ly/vagrant-docker-precise-dind | Ubuntu 12.04 Precise x86_64 based on the box above and ready to run [DinD](https://github.com/jpetazzo/dind) | 183 | 184 | 185 | ## Limitations 186 | 187 | As explained on the [networks](#networks) and [synced folder](#synced-folders) 188 | sections above, there are some "gotchas" when using the plugin that you need to have 189 | in mind before you start to pull your hair out. 190 | 191 | For instance, forwarded ports, synced folders and containers' hostnames will not be 192 | reconfigured on `vagrant reload`s if they have changed and the plugin **_will not 193 | give you any kind of warning or message_**. As an example, if you change your Puppet 194 | manifests / Chef cookbooks paths (which are shared / synced folders under the hood), 195 | **_you'll need to start from scratch_** (unless you make them NFS shared folders). 196 | This is due to a limitation in Docker itself as we can't change those parameters 197 | after the container has been created. 198 | 199 | Forwarded ports automatic collision handling is **_not supported as well_**. 200 | 201 | 202 | ## Contributing 203 | 204 | 1. Fork it 205 | 2. Create your feature branch (`git checkout -b my-new-feature`) 206 | 3. Commit your changes (`git commit -am 'Add some feature'`) 207 | 4. Push to the branch (`git push origin my-new-feature`) 208 | 5. Create new Pull Request 209 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require 'rspec/core/rake_task' 3 | 4 | namespace :spec do 5 | desc 'Run acceptance specs using Bats' 6 | task :acceptance do 7 | components = %w( 8 | basic network/forwarded_port synced_folder synced_folder/nfs 9 | provisioner/shell provisioner/puppet provisioner/chef-solo 10 | ).map{|s| "provider/docker/#{s}" } 11 | 12 | sh "bundle exec vagrant-spec test --components=#{components.join(' ')}" 13 | end 14 | 15 | require 'rspec/core/rake_task' 16 | desc "Run unit specs using RSpec" 17 | RSpec::Core::RakeTask.new('unit') do |t| 18 | t.pattern = "./unit/**/*_spec.rb" 19 | end 20 | end 21 | 22 | desc 'Run all specs' 23 | task :spec => ['spec:unit', 'spec:acceptance'] 24 | 25 | task :default => 'spec' 26 | -------------------------------------------------------------------------------- /boxes/dind/.gitignore: -------------------------------------------------------------------------------- 1 | Vagrantfile 2 | precise-dind.box 3 | -------------------------------------------------------------------------------- /boxes/dind/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM fgrehm/vagrant-ubuntu:precise 2 | 3 | RUN apt-get update && apt-get install lxc -yq --force-yes -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' 4 | 5 | RUN curl -sLS https://get.docker.io | sh 6 | 7 | RUN usermod -aG docker vagrant 8 | 9 | RUN curl -sLS https://raw.github.com/dotcloud/docker/master/hack/dind -o /dind && \ 10 | chmod +x /dind 11 | 12 | CMD ["/dind", "/sbin/init"] 13 | -------------------------------------------------------------------------------- /boxes/dind/README.md: -------------------------------------------------------------------------------- 1 | # Ubuntu Precise "Docker in Docker" enabled base box 2 | 3 | To turn this into a box: 4 | 5 | ``` 6 | docker build -t myuser/vagrant-ubuntu:precise-dind . 7 | docker push myuser/vagrant-ubuntu:precise-dind 8 | sed 's/IMAGE/myuser\/vagrant-ubuntu:precise-dind/' Vagrantfile.sample > Vagrantfile 9 | tar cvzf precise-dind.box ./metadata.json ./Vagrantfile 10 | ``` 11 | -------------------------------------------------------------------------------- /boxes/dind/Vagrantfile.sample: -------------------------------------------------------------------------------- 1 | Vagrant.configure("2") do |config| 2 | config.vm.provider :docker do |docker| 3 | docker.image = "IMAGE" 4 | docker.privileged = true 5 | docker.volumes << '/var/lib/docker' 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /boxes/dind/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "docker" 3 | } 4 | -------------------------------------------------------------------------------- /boxes/dummy/README.md: -------------------------------------------------------------------------------- 1 | # Dummy box 2 | 3 | Vagrant providers each require a custom provider-specific box format. This folder 4 | contains a "dummy" box that allows you to use the plugin without the need to create 5 | a custom base box. 6 | 7 | To turn this into a "box", run: 8 | 9 | ``` 10 | tar cvzf dummy.box ./metadata.json 11 | ``` 12 | 13 | By using this box you'll need to specify a Docker image on your `Vagrantfile` 14 | [as described](README.md#using-custom-images) on the README. 15 | 16 | For "real world" examples please check out [`boxes/precise`](boxes/precise) and 17 | [`boxes/nginx`](boxes/nginx). 18 | -------------------------------------------------------------------------------- /boxes/dummy/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "docker" 3 | } 4 | -------------------------------------------------------------------------------- /boxes/precise/.gitignore: -------------------------------------------------------------------------------- 1 | Vagrantfile 2 | -------------------------------------------------------------------------------- /boxes/precise/Dockerfile: -------------------------------------------------------------------------------- 1 | # Base Vagrant box 2 | 3 | FROM ubuntu-upstart:precise 4 | MAINTAINER Fabio Rehm "fgrehm@gmail.com" 5 | 6 | # Create and configure vagrant user 7 | RUN useradd --create-home -s /bin/bash vagrant 8 | WORKDIR /home/vagrant 9 | 10 | # Configure SSH access 11 | RUN mkdir -p /home/vagrant/.ssh 12 | RUN echo "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzIw+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoPkcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NOTd0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcWyLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQ== vagrant insecure public key" > /home/vagrant/.ssh/authorized_keys 13 | RUN chown -R vagrant: /home/vagrant/.ssh 14 | RUN adduser vagrant sudo 15 | RUN echo -n 'vagrant:vagrant' | chpasswd 16 | 17 | # Enable passwordless sudo for users under the "sudo" group 18 | RUN sed -i.bkp -e \ 19 | 's/%sudo\s\+ALL=(ALL\(:ALL\)\?)\s\+ALL/%sudo ALL=NOPASSWD:ALL/g' \ 20 | /etc/sudoers 21 | 22 | # Enable universe 23 | RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list 24 | 25 | # Thanks to http://docs.docker.io/en/latest/examples/running_ssh_service/ 26 | RUN mkdir /var/run/sshd 27 | 28 | # Update things and make sure the required packges are installed 29 | RUN apt-get update && \ 30 | apt-get install openssh-server sudo curl nfs-common -y && \ 31 | apt-get upgrade -y && \ 32 | apt-get clean 33 | 34 | # Puppet 35 | RUN wget http://apt.puppetlabs.com/puppetlabs-release-stable.deb -O /tmp/puppetlabs-release-stable.deb && \ 36 | dpkg -i /tmp/puppetlabs-release-stable.deb && \ 37 | apt-get update && \ 38 | apt-get install puppet puppet-common hiera facter virt-what -y --force-yes && \ 39 | rm -f /tmp/*.deb && \ 40 | apt-get clean 41 | 42 | # Chef 43 | RUN curl -L https://www.opscode.com/chef/install.sh -k | bash && apt-get clean 44 | -------------------------------------------------------------------------------- /boxes/precise/README.md: -------------------------------------------------------------------------------- 1 | # Ubuntu Precise base box 2 | 3 | This folder contains an example of a Dockerfile that builds an image ready for 4 | usage with Vagrant. Please check out the [source](boxes/precise/Dockerfile) 5 | for more information on building your own. 6 | 7 | To turn this into a box: 8 | 9 | ``` 10 | docker build -t myuser/vagrant-ubuntu:precise . 11 | docker push myuser/vagrant-ubuntu:precise 12 | sed 's/IMAGE/myuser\/vagrant-ubuntu:precise/' Vagrantfile.sample > Vagrantfile 13 | tar cvzf precise.box ./metadata.json ./Vagrantfile 14 | ``` 15 | 16 | This box works by using Vagrant's built-in `Vagrantfile` merging to setup defaults 17 | for Docker. These defaults can easily be overwritten by higher-level Vagrantfiles 18 | (such as project root Vagrantfiles). 19 | -------------------------------------------------------------------------------- /boxes/precise/Vagrantfile.sample: -------------------------------------------------------------------------------- 1 | Vagrant.configure("2") do |config| 2 | config.vm.provider :docker do |docker| 3 | docker.image = "IMAGE" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /boxes/precise/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "docker" 3 | } 4 | -------------------------------------------------------------------------------- /development/Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | Vagrant.require_plugin 'vagrant-cachier' 5 | Vagrant.require_plugin 'vagrant-notify' 6 | Vagrant.require_plugin 'vagrant-lxc' 7 | Vagrant.require_plugin 'vagrant-pristine' 8 | Vagrant.require_plugin 'vagrant-global-status' 9 | Vagrant.require_plugin 'ventriloquist' 10 | 11 | # Vagrantfile API/syntax version. Don't touch unless you know what you're doing! 12 | VAGRANTFILE_API_VERSION = "2" 13 | 14 | Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| 15 | config.vm.box = "raring64" 16 | config.vm.synced_folder "../", "/vagrant", id: 'vagrant-root' 17 | 18 | config.cache.auto_detect = true 19 | 20 | config.vm.provider :lxc do |lxc, override| 21 | # Required to boot nested containers 22 | lxc.customize 'aa_profile', 'unconfined' 23 | override.vm.box_url = 'http://bit.ly/vagrant-lxc-raring64-2013-10-23' 24 | end 25 | 26 | config.vm.provider :virtualbox do |vb, override| 27 | vb.customize [ "modifyvm", :id, "--memory", 1536, "--cpus", "2" ] 28 | override.vm.box_url = 'http://cloud-images.ubuntu.com/vagrant/raring/current/raring-server-cloudimg-amd64-vagrant-disk1.box' 29 | override.vm.network :private_network, ip: "192.168.50.102" 30 | end 31 | 32 | # Required to boot nested containers 33 | config.vm.provision :shell, inline: %[ 34 | if ! [ -f /etc/default/lxc ]; then 35 | cat < /etc/default/lxc 36 | LXC_AUTO="true" 37 | USE_LXC_BRIDGE="true" 38 | LXC_BRIDGE="lxcbr0" 39 | LXC_ADDR="10.0.240.1" 40 | LXC_NETMASK="255.255.255.0" 41 | LXC_NETWORK="10.0.240.0/24" 42 | LXC_DHCP_RANGE="10.0.240.2,10.0.240.254" 43 | LXC_DHCP_MAX="253" 44 | LXC_SHUTDOWN_TIMEOUT=120 45 | STR 46 | fi 47 | ] 48 | 49 | # Install docker on the machine (powered by Vocker) 50 | config.vm.provision :docker do |docker| 51 | docker.pull_images 'ubuntu:precise' 52 | end 53 | 54 | # Configure Ruby so that we can test the plugin from within the VM 55 | config.vm.provision :ventriloquist do |env| 56 | env.platforms << 'ruby:2.0.0' 57 | end 58 | 59 | config.vm.provision :shell, privileged: false, inline: %[ 60 | # Bundle! 61 | cd /vagrant && bundle install 62 | 63 | # Because we are lazy 64 | if ! $(grep 'cd /vagrant' -q $HOME/.bashrc); then 65 | echo 'alias be="bundle exec"' >> $HOME/.bashrc 66 | echo 'alias vagrant="bundle exec vagrant"' >> $HOME/.bashrc 67 | echo 'cd /vagrant' >> $HOME/.bashrc 68 | fi 69 | 70 | if ! [ -d $HOME/.vagrant.d/boxes ]; then 71 | # This is a dummy base box "built" by hand 72 | mkdir -p $HOME/.vagrant.d/boxes/dummy/docker 73 | echo '{ "provider": "docker" }' > $HOME/.vagrant.d/boxes/dummy/docker/metadata.json 74 | fi 75 | 76 | if ! $(which bsdtar > /dev/null 2>/dev/null); then 77 | sudo apt-get install bsdtar -y 78 | fi 79 | 80 | echo -n "\n\n----\n" 81 | echo 'If this is the first time you are provisioning the VM, please `bundle exec vagrant reload` it ;)' 82 | ] 83 | end 84 | -------------------------------------------------------------------------------- /docker-provider.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'docker-provider/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "docker-provider" 8 | spec.version = VagrantPlugins::DockerProvider::VERSION 9 | spec.authors = ["Fabio Rehm"] 10 | spec.email = ["fgrehm@gmail.com"] 11 | spec.description = %q{Docker provider for Vagrant} 12 | spec.summary = spec.description 13 | spec.homepage = "https://github.com/fgrehm/docker-provider" 14 | spec.license = "MIT" 15 | 16 | spec.files = `git ls-files`.split($/) 17 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 18 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 19 | spec.require_paths = ["lib"] 20 | 21 | spec.add_development_dependency "bundler", "~> 1.3" 22 | spec.add_development_dependency "rake" 23 | spec.add_development_dependency "rspec" 24 | end 25 | -------------------------------------------------------------------------------- /example/Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | ENV['VAGRANT_DEFAULT_PROVIDER'] = 'docker' 5 | 6 | # This is only needed if you are using the plugin from sources with bundler 7 | Vagrant.require_plugin 'docker-provider' 8 | Vagrant.require_plugin 'vagrant-cachier' 9 | 10 | # Vagrantfile API/syntax version. Don't touch unless you know what you're doing! 11 | VAGRANTFILE_API_VERSION = "2" 12 | 13 | Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| 14 | config.cache.scope = :machine 15 | 16 | # This fires up a container with a SSH server 17 | config.vm.define 'dummy' do |node| 18 | node.vm.box = 'dummy' 19 | node.vm.box_url = 'http://bit.ly/vagrant-docker-dummy' 20 | 21 | node.vm.provider :docker do |docker| 22 | docker.image = 'fgrehm/vagrant-ubuntu:precise' 23 | docker.privileged = true 24 | docker.volumes << '/var/lib/docker' 25 | end 26 | 27 | node.cache.synced_folder_opts = { 28 | type: :nfs, mount_options: ['rw', 'vers=3', 'tcp', 'nolock'] 29 | } 30 | end 31 | 32 | # This is a container that gets provisioned with Puppet 33 | config.vm.define 'precise' do |node| 34 | node.vm.box = 'precise64' 35 | node.vm.box_url = 'http://bit.ly/vagrant-docker-precise' 36 | 37 | node.vm.provision :puppet do |puppet| 38 | puppet.manifests_path = "." 39 | puppet.manifest_file = "site.pp" 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /example/site.pp: -------------------------------------------------------------------------------- 1 | package { 'htop': } 2 | -------------------------------------------------------------------------------- /lib/docker-provider.rb: -------------------------------------------------------------------------------- 1 | require_relative "docker-provider/version" 2 | require_relative "docker-provider/plugin" 3 | -------------------------------------------------------------------------------- /lib/docker-provider/action.rb: -------------------------------------------------------------------------------- 1 | require_relative 'action/check_running' 2 | require_relative 'action/created' 3 | require_relative 'action/create' 4 | require_relative 'action/destroy' 5 | require_relative 'action/forward_ports' 6 | require_relative 'action/stop' 7 | require_relative 'action/message' 8 | require_relative 'action/prepare_nfs_valid_ids' 9 | require_relative 'action/prepare_nfs_settings' 10 | require_relative 'action/is_running' 11 | require_relative 'action/start' 12 | 13 | module VagrantPlugins 14 | module DockerProvider 15 | module Action 16 | # Shortcuts 17 | Builtin = Vagrant::Action::Builtin 18 | Builder = Vagrant::Action::Builder 19 | 20 | # This action brings the "machine" up from nothing, including creating the 21 | # container, configuring metadata, and booting. 22 | def self.action_up 23 | Builder.new.tap do |b| 24 | b.use Builtin::ConfigValidate 25 | b.use Builtin::Call, Created do |env, b2| 26 | if !env[:result] 27 | b2.use Builtin::HandleBoxUrl 28 | # TODO: Find out where this fits into the process 29 | # b2.use Builtin::EnvSet, :port_collision_repair => true 30 | # b2.use Builtin::HandleForwardedPortCollisions 31 | b2.use Builtin::Provision 32 | b2.use PrepareNFSValidIds 33 | b2.use Builtin::SyncedFolderCleanup 34 | b2.use Builtin::SyncedFolders 35 | b2.use PrepareNFSSettings 36 | b2.use ForwardPorts 37 | # This will actually create and start, but that's fine 38 | b2.use Create 39 | b2.use action_boot 40 | else 41 | b2.use PrepareNFSValidIds 42 | b2.use Builtin::SyncedFolderCleanup 43 | b2.use Builtin::SyncedFolders 44 | b2.use PrepareNFSSettings 45 | b2.use action_start 46 | end 47 | end 48 | end 49 | end 50 | 51 | # This action just runs the provisioners on the machine. 52 | def self.action_provision 53 | Builder.new.tap do |b| 54 | b.use Builtin::ConfigValidate 55 | b.use Builtin::Call, Created do |env1, b2| 56 | if !env1[:result] 57 | b2.use Message, :not_created 58 | next 59 | end 60 | 61 | b2.use Builtin::Call, IsRunning do |env2, b3| 62 | if !env2[:result] 63 | b3.use Message, :not_running 64 | next 65 | end 66 | 67 | b3.use Builtin::Provision 68 | end 69 | end 70 | end 71 | end 72 | 73 | # This is the action that is primarily responsible for halting 74 | # the virtual machine, gracefully or by force. 75 | def self.action_halt 76 | Builder.new.tap do |b| 77 | b.use Builtin::Call, Created do |env, b2| 78 | if env[:result] 79 | b2.use Builtin::Call, Builtin::GracefulHalt, :stopped, :running do |env2, b3| 80 | if !env2[:result] 81 | b3.use Stop 82 | end 83 | end 84 | else 85 | b2.use Message, :not_created 86 | end 87 | end 88 | end 89 | end 90 | 91 | # This action is responsible for reloading the machine, which 92 | # brings it down, sucks in new configuration, and brings the 93 | # machine back up with the new configuration. 94 | def self.action_reload 95 | Builder.new.tap do |b| 96 | b.use Builtin::Call, Created do |env1, b2| 97 | if !env1[:result] 98 | b2.use Message, :not_created 99 | next 100 | end 101 | 102 | b2.use Builtin::ConfigValidate 103 | b2.use action_halt 104 | b2.use action_start 105 | end 106 | end 107 | end 108 | 109 | # This is the action that is primarily responsible for completely 110 | # freeing the resources of the underlying virtual machine. 111 | def self.action_destroy 112 | Builder.new.tap do |b| 113 | b.use Builtin::Call, Created do |env1, b2| 114 | if !env1[:result] 115 | b2.use Message, :not_created 116 | next 117 | end 118 | 119 | b2.use Builtin::Call, Builtin::DestroyConfirm do |env2, b3| 120 | if env2[:result] 121 | b3.use Builtin::ConfigValidate 122 | b3.use Builtin::EnvSet, :force_halt => true 123 | b3.use action_halt 124 | b3.use Destroy 125 | b3.use Builtin::ProvisionerCleanup 126 | else 127 | b3.use Message, :will_not_destroy 128 | end 129 | end 130 | end 131 | end 132 | end 133 | 134 | # This is the action that will exec into an SSH shell. 135 | def self.action_ssh 136 | Builder.new.tap do |b| 137 | b.use CheckRunning 138 | b.use Builtin::SSHExec 139 | end 140 | end 141 | 142 | # This is the action that will run a single SSH command. 143 | def self.action_ssh_run 144 | Builder.new.tap do |b| 145 | b.use CheckRunning 146 | b.use Builtin::SSHRun 147 | end 148 | end 149 | 150 | def self.action_start 151 | Builder.new.tap do |b| 152 | b.use Builtin::ConfigValidate 153 | b.use Builtin::Call, IsRunning do |env, b2| 154 | # If the container is running, then our work here is done, exit 155 | next if env[:result] 156 | 157 | b2.use Builtin::Provision 158 | b2.use Message, :starting 159 | b2.use action_boot 160 | end 161 | end 162 | end 163 | 164 | def self.action_boot 165 | Builder.new.tap do |b| 166 | # TODO: b.use Builtin::SetHostname 167 | b.use Start 168 | b.use Builtin::WaitForCommunicator 169 | end 170 | end 171 | end 172 | end 173 | end 174 | -------------------------------------------------------------------------------- /lib/docker-provider/action/check_running.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module DockerProvider 3 | module Action 4 | class CheckRunning 5 | def initialize(app, env) 6 | @app = app 7 | end 8 | 9 | def call(env) 10 | if env[:machine].state.id == :not_created 11 | raise Vagrant::Errors::VMNotCreatedError 12 | end 13 | 14 | if env[:machine].state.id == :stopped 15 | raise Vagrant::Errors::VMNotRunningError 16 | end 17 | 18 | # Call the next if we have one (but we shouldn't, since this 19 | # middleware is built to run with the Call-type middlewares) 20 | @app.call(env) 21 | end 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/docker-provider/action/create.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module DockerProvider 3 | module Action 4 | class Create 5 | def initialize(app, env) 6 | @app = app 7 | @@mutex ||= Mutex.new 8 | end 9 | 10 | def call(env) 11 | @env = env 12 | @machine = env[:machine] 13 | @provider_config = @machine.provider_config 14 | @machine_config = @machine.config 15 | @driver = @machine.provider.driver 16 | 17 | guard_cmd_configured! 18 | 19 | cid = '' 20 | @@mutex.synchronize do 21 | cid = @driver.create(create_params) 22 | end 23 | 24 | @machine.id = cid 25 | @app.call(env) 26 | end 27 | 28 | def create_params 29 | container_name = "#{@env[:root_path].basename.to_s}_#{@machine.name}" 30 | container_name.gsub!(/[^-a-z0-9_]/i, "") 31 | container_name << "_#{Time.now.to_i}" 32 | 33 | { 34 | image: @provider_config.image, 35 | cmd: @provider_config.cmd, 36 | ports: forwarded_ports, 37 | name: container_name, 38 | hostname: @machine_config.vm.hostname, 39 | volumes: @provider_config.volumes, 40 | privileged: @provider_config.privileged 41 | } 42 | end 43 | 44 | def forwarded_ports 45 | @env[:forwarded_ports].map do |fp| 46 | # TODO: Support for the protocol argument 47 | "#{fp[:host]}:#{fp[:guest]}" 48 | end.compact 49 | end 50 | 51 | def guard_cmd_configured! 52 | if ! @provider_config.image 53 | raise Errors::ImageNotConfiguredError, name: @machine.name 54 | end 55 | end 56 | end 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /lib/docker-provider/action/created.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module DockerProvider 3 | module Action 4 | class Created 5 | def initialize(app, env) 6 | @app = app 7 | end 8 | 9 | def call(env) 10 | machine = env[:machine] 11 | driver = machine.provider.driver 12 | env[:result] = machine.id && driver.created?(machine.id) 13 | @app.call(env) 14 | end 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/docker-provider/action/destroy.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module DockerProvider 3 | module Action 4 | class Destroy 5 | def initialize(app, env) 6 | @app = app 7 | end 8 | 9 | def call(env) 10 | env[:ui].info I18n.t("vagrant.actions.vm.destroy.destroying") 11 | 12 | machine = env[:machine] 13 | config = machine.provider_config 14 | driver = machine.provider.driver 15 | 16 | driver.rm(machine.id) 17 | machine.id = nil 18 | 19 | @app.call env 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/docker-provider/action/forward_ports.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module DockerProvider 3 | module Action 4 | class ForwardPorts 5 | def initialize(app, env) 6 | @app = app 7 | end 8 | 9 | def call(env) 10 | @env = env 11 | 12 | env[:forwarded_ports] = compile_forwarded_ports(env[:machine].config) 13 | 14 | if env[:forwarded_ports].any? 15 | env[:ui].info I18n.t("vagrant.actions.vm.forward_ports.forwarding") 16 | inform_forwarded_ports(env[:forwarded_ports]) 17 | end 18 | 19 | # FIXME: Check whether the container has already been created with 20 | # different exposed ports and let the user know about it 21 | 22 | @app.call env 23 | end 24 | 25 | def inform_forwarded_ports(ports) 26 | ports.each do |fp| 27 | message_attributes = { 28 | :adapter => 'eth0', 29 | :guest_port => fp[:guest], 30 | :host_port => fp[:host] 31 | } 32 | 33 | @env[:ui].info(I18n.t("vagrant.actions.vm.forward_ports.forwarding_entry", 34 | message_attributes)) 35 | end 36 | end 37 | 38 | private 39 | 40 | def compile_forwarded_ports(config) 41 | mappings = {} 42 | 43 | config.vm.networks.each do |type, options| 44 | if type == :forwarded_port && options[:id] != 'ssh' 45 | mappings[options[:host]] = options 46 | end 47 | end 48 | 49 | mappings.values 50 | end 51 | end 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/docker-provider/action/is_running.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module DockerProvider 3 | module Action 4 | class IsRunning 5 | def initialize(app, env) 6 | @app = app 7 | end 8 | 9 | def call(env) 10 | machine = env[:machine] 11 | driver = machine.provider.driver 12 | 13 | env[:result] = driver.running?(machine.id) 14 | 15 | @app.call(env) 16 | end 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/docker-provider/action/message.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module DockerProvider 3 | module Action 4 | # XXX: Is this really needed? Should we contribute this back to Vagrant's core? 5 | class Message 6 | def initialize(app, env, msg_key, type = :info) 7 | @app = app 8 | @msg_key = msg_key 9 | @type = type 10 | end 11 | 12 | def call(env) 13 | machine = env[:machine] 14 | message = I18n.t("docker_provider.messages.#{@msg_key}", name: machine.name) 15 | 16 | env[:ui].send @type, message 17 | 18 | @app.call env 19 | end 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/docker-provider/action/prepare_nfs_settings.rb: -------------------------------------------------------------------------------- 1 | require_relative '../errors' 2 | 3 | module VagrantPlugins 4 | module DockerProvider 5 | module Action 6 | class PrepareNFSSettings 7 | include Vagrant::Util::Retryable 8 | 9 | def initialize(app, env) 10 | @app = app 11 | @logger = Log4r::Logger.new("vagrant::action::vm::nfs") 12 | end 13 | 14 | def call(env) 15 | @machine = env[:machine] 16 | 17 | @app.call(env) 18 | 19 | if using_nfs? && !privileged_container? 20 | raise Errors::NfsWithoutPrivilegedError 21 | end 22 | 23 | if using_nfs? 24 | @logger.info("Using NFS, preparing NFS settings by reading host IP and machine IP") 25 | add_ips_to_env!(env) 26 | end 27 | end 28 | 29 | # We're using NFS if we have any synced folder with NFS configured. If 30 | # we are not using NFS we don't need to do the extra work to 31 | # populate these fields in the environment. 32 | def using_nfs? 33 | @machine.config.vm.synced_folders.any? { |_, opts| opts[:type] == :nfs } 34 | end 35 | 36 | def privileged_container? 37 | @machine.provider.driver.privileged?(@machine.id) 38 | end 39 | 40 | # Extracts the proper host and guest IPs for NFS mounts and stores them 41 | # in the environment for the SyncedFolder action to use them in 42 | # mounting. 43 | # 44 | # The ! indicates that this method modifies its argument. 45 | def add_ips_to_env!(env) 46 | provider = env[:machine].provider 47 | 48 | host_ip = provider.driver.docker_bridge_ip 49 | machine_ip = provider.ssh_info[:host] 50 | 51 | raise Vagrant::Errors::NFSNoHostonlyNetwork if !host_ip || !machine_ip 52 | 53 | env[:nfs_host_ip] = host_ip 54 | env[:nfs_machine_ip] = machine_ip 55 | end 56 | end 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /lib/docker-provider/action/prepare_nfs_valid_ids.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module DockerProvider 3 | module Action 4 | class PrepareNFSValidIds 5 | def initialize(app, env) 6 | @app = app 7 | @logger = Log4r::Logger.new("vagrant::action::vm::nfs") 8 | end 9 | 10 | def call(env) 11 | machine = env[:machine] 12 | env[:nfs_valid_ids] = machine.provider.driver.all_containers 13 | 14 | @app.call(env) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/docker-provider/action/start.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module DockerProvider 3 | module Action 4 | class Start 5 | def initialize(app, env) 6 | @app = app 7 | end 8 | 9 | def call(env) 10 | machine = env[:machine] 11 | driver = machine.provider.driver 12 | driver.start(machine.id) 13 | @app.call(env) 14 | end 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/docker-provider/action/stop.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module DockerProvider 3 | module Action 4 | class Stop 5 | def initialize(app, env) 6 | @app = app 7 | end 8 | 9 | def call(env) 10 | machine = env[:machine] 11 | driver = machine.provider.driver 12 | if driver.running?(machine.id) 13 | env[:ui].info I18n.t("docker_provider.messages.stopping") 14 | driver.stop(machine.id) 15 | end 16 | @app.call(env) 17 | end 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/docker-provider/config.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module DockerProvider 3 | class Config < Vagrant.plugin("2", :config) 4 | attr_accessor :image, :cmd, :ports, :volumes, :privileged 5 | 6 | def initialize 7 | @image = nil 8 | @cmd = UNSET_VALUE 9 | @ports = [] 10 | @privileged = UNSET_VALUE 11 | @volumes = [] 12 | end 13 | 14 | def finalize! 15 | @cmd = [] if @cmd == UNSET_VALUE 16 | @privileged = false if @privileged == UNSET_VALUE 17 | end 18 | 19 | def validate(machine) 20 | errors = _detected_errors 21 | 22 | # TODO: Detect if base image has a CMD / ENTRYPOINT set before erroring out 23 | errors << I18n.t("docker_provider.errors.config.cmd_not_set") if @cmd == UNSET_VALUE 24 | 25 | { "docker-provider" => errors } 26 | end 27 | 28 | private 29 | 30 | def using_nfs?(machine) 31 | machine.config.vm.synced_folders.any? { |_, opts| opts[:type] == :nfs } 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/docker-provider/driver.rb: -------------------------------------------------------------------------------- 1 | require "vagrant/util/busy" 2 | require "vagrant/util/subprocess" 3 | require "vagrant/util/retryable" 4 | 5 | require 'log4r' 6 | require 'json' 7 | 8 | module VagrantPlugins 9 | module DockerProvider 10 | class Driver 11 | include Vagrant::Util::Retryable 12 | 13 | def initialize 14 | @logger = Log4r::Logger.new("vagrant::docker::driver") 15 | end 16 | 17 | def create(params) 18 | image = params.fetch(:image) 19 | ports = Array(params[:ports]) 20 | volumes = Array(params[:volumes]) 21 | name = params.fetch(:name) 22 | cmd = Array(params.fetch(:cmd)) 23 | 24 | run_cmd = %W(docker run -name #{name} -d) 25 | run_cmd += ports.map { |p| ['-p', p.to_s] } 26 | run_cmd += volumes.map { |v| ['-v', v.to_s] } 27 | run_cmd += %W(-privileged) if params[:privileged] 28 | run_cmd += %W(-h #{params[:hostname]}) if params[:hostname] 29 | run_cmd += [image, cmd] 30 | 31 | retryable(tries: 10, sleep: 1) do 32 | execute(*run_cmd.flatten).chomp 33 | end 34 | end 35 | 36 | def state(cid) 37 | case 38 | when running?(cid) 39 | :running 40 | when created?(cid) 41 | :stopped 42 | else 43 | :not_created 44 | end 45 | end 46 | 47 | def created?(cid) 48 | result = execute('docker', 'ps', '-a', '-q', '-notrunc').to_s 49 | result =~ /^#{Regexp.escape cid}$/ 50 | end 51 | 52 | def running?(cid) 53 | result = execute('docker', 'ps', '-q', '-notrunc') 54 | result =~ /^#{Regexp.escape cid}$/m 55 | end 56 | 57 | def privileged?(cid) 58 | inspect_container(cid)['HostConfig']['Privileged'] 59 | end 60 | 61 | def start(cid) 62 | unless running?(cid) 63 | execute('docker', 'start', cid) 64 | # This resets the cached information we have around, allowing `vagrant reload`s 65 | # to work properly 66 | # TODO: Add spec to verify this behavior 67 | @data = nil 68 | end 69 | end 70 | 71 | def stop(cid) 72 | if running?(cid) 73 | execute('docker', 'stop', '-t', '1', cid) 74 | end 75 | end 76 | 77 | def rm(cid) 78 | if created?(cid) 79 | execute('docker', 'rm', '-v', cid) 80 | end 81 | end 82 | 83 | def inspect_container(cid) 84 | # DISCUSS: Is there a chance that this json will change after the container 85 | # has been brought up? 86 | @data ||= JSON.parse(execute('docker', 'inspect', cid)).first 87 | end 88 | 89 | def all_containers 90 | execute('docker', 'ps', '-a', '-q', '-notrunc').to_s.split 91 | end 92 | 93 | def docker_bridge_ip 94 | output = execute('/sbin/ip', '-4', 'addr', 'show', 'scope', 'global', 'docker0') 95 | if output =~ /^\s+inet ([0-9.]+)\/[0-9]+\s+/ 96 | return $1.to_s 97 | else 98 | # TODO: Raise an user friendly message 99 | raise 'Unable to fetch docker bridge IP!' 100 | end 101 | end 102 | 103 | private 104 | 105 | def execute(*cmd, &block) 106 | result = raw(*cmd, &block) 107 | 108 | if result.exit_code != 0 109 | if @interrupted 110 | @logger.info("Exit code != 0, but interrupted. Ignoring.") 111 | else 112 | msg = result.stdout.gsub("\r\n", "\n") 113 | msg << result.stderr.gsub("\r\n", "\n") 114 | raise "#{cmd.inspect}\n#{msg}" #Errors::ExecuteError, :command => command.inspect 115 | end 116 | end 117 | 118 | # Return the output, making sure to replace any Windows-style 119 | # newlines with Unix-style. 120 | result.stdout.gsub("\r\n", "\n") 121 | end 122 | 123 | def raw(*cmd, &block) 124 | int_callback = lambda do 125 | @interrupted = true 126 | @logger.info("Interrupted.") 127 | end 128 | 129 | # Append in the options for subprocess 130 | cmd << { :notify => [:stdout, :stderr] } 131 | 132 | Vagrant::Util::Busy.busy(int_callback) do 133 | Vagrant::Util::Subprocess.execute(*cmd, &block) 134 | end 135 | end 136 | end 137 | end 138 | end 139 | -------------------------------------------------------------------------------- /lib/docker-provider/errors.rb: -------------------------------------------------------------------------------- 1 | require 'vagrant/errors' 2 | 3 | module VagrantPlugins 4 | module DockerProvider 5 | module Errors 6 | class ImageNotConfiguredError < Vagrant::Errors::VagrantError 7 | error_key(:docker_provider_image_not_configured) 8 | end 9 | class NfsWithoutPrivilegedError < Vagrant::Errors::VagrantError 10 | error_key(:docker_provider_nfs_without_privileged) 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/docker-provider/plugin.rb: -------------------------------------------------------------------------------- 1 | # TODO: Switch to Vagrant.require_version before 1.0.0 2 | # see: https://github.com/mitchellh/vagrant/blob/bc55081e9ffaa6820113e449a9f76b293a29b27d/lib/vagrant.rb#L202-L228 3 | unless Gem::Requirement.new('>= 1.4.0').satisfied_by?(Gem::Version.new(Vagrant::VERSION)) 4 | raise 'docker-provider requires Vagrant >= 1.4.0 in order to work!' 5 | end 6 | 7 | I18n.load_path << File.expand_path(File.dirname(__FILE__) + '/../../locales/en.yml') 8 | I18n.reload! 9 | 10 | module VagrantPlugins 11 | module DockerProvider 12 | class Plugin < Vagrant.plugin("2") 13 | name "docker-provider" 14 | 15 | provider(:docker, parallel: true) do 16 | require_relative 'provider' 17 | Provider 18 | end 19 | 20 | config(:docker, :provider) do 21 | require_relative 'config' 22 | Config 23 | end 24 | 25 | synced_folder(:docker) do 26 | require File.expand_path("../synced_folder", __FILE__) 27 | SyncedFolder 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/docker-provider/provider.rb: -------------------------------------------------------------------------------- 1 | require_relative 'driver' 2 | require_relative 'action' 3 | 4 | module VagrantPlugins 5 | module DockerProvider 6 | class Provider < Vagrant.plugin("2", :provider) 7 | attr_reader :driver 8 | 9 | def initialize(machine) 10 | @logger = Log4r::Logger.new("vagrant::provider::docker") 11 | @machine = machine 12 | @driver = Driver.new 13 | end 14 | 15 | # @see Vagrant::Plugin::V2::Provider#action 16 | def action(name) 17 | action_method = "action_#{name}" 18 | return Action.send(action_method) if Action.respond_to?(action_method) 19 | nil 20 | end 21 | 22 | # Returns the SSH info for accessing the Container. 23 | def ssh_info 24 | # If the Container is not created then we cannot possibly SSH into it, so 25 | # we return nil. 26 | return nil if state == :not_created 27 | 28 | network = @driver.inspect_container(@machine.id)['NetworkSettings'] 29 | ip = network['IPAddress'] 30 | 31 | # If we were not able to identify the container's IP, we return nil 32 | # here and we let Vagrant core deal with it ;) 33 | return nil unless ip 34 | 35 | { 36 | :host => ip, 37 | :port => @machine.config.ssh.guest_port 38 | } 39 | end 40 | 41 | def state 42 | state_id = nil 43 | state_id = :not_created if !@machine.id || !@driver.created?(@machine.id) 44 | state_id = @driver.state(@machine.id) if @machine.id && !state_id 45 | state_id = :unknown if !state_id 46 | 47 | short = state_id.to_s.gsub("_", " ") 48 | long = I18n.t("vagrant.commands.status.#{state_id}") 49 | 50 | Vagrant::MachineState.new(state_id, short, long) 51 | end 52 | 53 | def to_s 54 | id = @machine.id ? @machine.id : "new container" 55 | "Docker (#{id})" 56 | end 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /lib/docker-provider/synced_folder.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module DockerProvider 3 | class SyncedFolder < Vagrant.plugin("2", :synced_folder) 4 | def usable?(machine) 5 | # These synced folders only work if the provider is Docker 6 | machine.provider_name == :docker 7 | end 8 | 9 | def prepare(machine, folders, _opts) 10 | # FIXME: Check whether the container has already been created with 11 | # different synced folders and let the user know about it 12 | folders.each do |id, data| 13 | host_path = File.expand_path(data[:hostpath], machine.env.root_path) 14 | guest_path = data[:guestpath] 15 | machine.provider_config.volumes << "#{host_path}:#{guest_path}" 16 | end 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/docker-provider/version.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module DockerProvider 3 | VERSION = "0.1.0" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /locales/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | docker_provider: 3 | messages: 4 | not_created: |- 5 | The container hasn't been created yet. 6 | not_running: |- 7 | The container is not currently running. 8 | will_not_destroy: |- 9 | The container '%{name}' will not be destroyed, since the confirmation 10 | was declined. 11 | starting: |- 12 | Starting container... 13 | stopping: |- 14 | Stopping container... 15 | container_ready: |- 16 | Container started and ready for use! 17 | 18 | errors: 19 | config: 20 | cmd_not_set: |- 21 | The Docker command has not been set! 22 | 23 | vagrant: 24 | errors: 25 | docker_provider_nfs_without_privileged: |- 26 | You've configured a NFS synced folder but didn't enable privileged 27 | mode for the container. Please set the `privileged` option to true 28 | on the provider block from your Vagrantfile, recreate the container 29 | and try again. 30 | 31 | docker_provider_image_not_configured: |- 32 | The base Docker image has not been set for the '%{name}' VM! 33 | -------------------------------------------------------------------------------- /spec/acceptance/provider/basic_spec.rb: -------------------------------------------------------------------------------- 1 | # This tests the basic functionality of a provider: that it can run 2 | # a machine, provide SSH access, and destroy that machine. 3 | shared_examples "provider/basic" do |provider, options| 4 | if !options[:box] 5 | raise ArgumentError, 6 | "box option must be specified for provider: #{provider}" 7 | end 8 | 9 | include_context "acceptance" 10 | 11 | before do 12 | assert_execute("vagrant", "box", "add", "box", options[:box]) 13 | assert_execute("vagrant", "init", "box") 14 | vagrantfile = environment.workdir.join('Vagrantfile') 15 | # TODO: Can we just shell out to something? 16 | new_vagrantfile = "Vagrant.require_plugin('docker-provider')\n#{vagrantfile.read}" 17 | vagrantfile.open('w') { |f| f.puts(new_vagrantfile) } 18 | end 19 | 20 | after do 21 | # Just always do this just in case 22 | execute("vagrant", "destroy", "--force", log: false) 23 | end 24 | 25 | def assert_running 26 | result = execute("vagrant", "ssh", "-c", "echo foo") 27 | expect(result).to exit_with(0) 28 | expect(result.stdout).to match(/foo\n$/) 29 | end 30 | 31 | def assert_not_running 32 | result = execute("vagrant", "ssh", "-c", "echo foo") 33 | expect(result).to exit_with(1) 34 | end 35 | 36 | =begin 37 | TODO(mitchellh): These all exit with exit code 0. Unsure if bug. 38 | 39 | it "can't halt before an up" do 40 | expect(execute("vagrant", "halt")).to exit_with(1) 41 | end 42 | 43 | it "can't resume before an up" do 44 | expect(execute("vagrant", "resume")).to exit_with(1) 45 | end 46 | 47 | it "can't suspend before an up" do 48 | expect(execute("vagrant", "suspend")).to exit_with(1) 49 | end 50 | =end 51 | 52 | context "after an up" do 53 | before do 54 | assert_execute("vagrant", "up", "--provider=#{provider}") 55 | end 56 | 57 | after do 58 | assert_execute("vagrant", "destroy", "--force") 59 | end 60 | 61 | it "can manage machine lifecycle" do 62 | status("Test: machine is running after up") 63 | assert_running 64 | 65 | if !options[:features].include?("!suspend") 66 | status("Test: suspend") 67 | assert_execute("vagrant", "suspend") 68 | 69 | status("Test: ssh doesn't work during suspended state") 70 | assert_not_running 71 | 72 | status("Test: resume after suspend") 73 | assert_execute("vagrant", "resume") 74 | assert_running 75 | else 76 | status("Not testing 'suspend', provider doesn't support it") 77 | end 78 | 79 | if !options[:features].include?("!halt") 80 | status("Test: halt") 81 | assert_execute("vagrant", "halt") 82 | 83 | status("Test: ssh doesn't work during halted state") 84 | assert_not_running 85 | 86 | status("Test: up after halt") 87 | assert_execute("vagrant", "up") 88 | assert_running 89 | else 90 | status("Not testing 'halt', provider doesn't support it") 91 | end 92 | end 93 | end 94 | end 95 | -------------------------------------------------------------------------------- /spec/acceptance/provider/network_forwarded_port_spec.rb: -------------------------------------------------------------------------------- 1 | shared_examples "provider/network/forwarded_port" do |provider, options| 2 | if !options[:box] 3 | raise ArgumentError, 4 | "box option must be specified for provider: #{provider}" 5 | end 6 | 7 | include_context "acceptance" 8 | 9 | before do 10 | environment.skeleton("network_forwarded_port") 11 | 12 | vagrantfile = environment.workdir.join('Vagrantfile') 13 | # TODO: Can we just shell out to something? 14 | new_vagrantfile = "Vagrant.require_plugin('docker-provider')\n#{vagrantfile.read}" 15 | vagrantfile.open('w') { |f| f.puts(new_vagrantfile) } 16 | 17 | assert_execute("vagrant", "box", "add", "box", options[:box]) 18 | assert_execute("vagrant", "up", "--provider=#{provider}") 19 | end 20 | 21 | after do 22 | assert_execute("vagrant", "destroy", "--force") 23 | end 24 | 25 | it "properly configures forwarded ports" do 26 | status("Test: TCP forwarded port (default)") 27 | assert_network("http://localhost:8080/", 8080) 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/acceptance/provider/synced_folder_spec.rb: -------------------------------------------------------------------------------- 1 | # This tests that synced folders work with a given provider. 2 | shared_examples "provider/synced_folder" do |provider, options| 3 | if !options[:box] 4 | raise ArgumentError, 5 | "box option must be specified for provider: #{provider}" 6 | end 7 | 8 | include_context "acceptance" 9 | 10 | before do 11 | environment.skeleton("synced_folders") 12 | 13 | vagrantfile = environment.workdir.join('Vagrantfile') 14 | # TODO: Can we just shell out to something? 15 | new_vagrantfile = "Vagrant.require_plugin('docker-provider')\n#{vagrantfile.read}" 16 | vagrantfile.open('w') { |f| f.puts(new_vagrantfile) } 17 | 18 | assert_execute("vagrant", "box", "add", "basic", options[:box]) 19 | assert_execute("vagrant", "up", "--provider=#{provider}") 20 | end 21 | 22 | after do 23 | assert_execute("vagrant", "destroy", "--force") 24 | end 25 | 26 | # We put all of this in a single RSpec test so that we can test all 27 | # the cases within a single VM rather than having to `vagrant up` many 28 | # times. 29 | it "properly configures synced folder types" do 30 | status("Test: mounts the default /vagrant synced folder") 31 | result = execute("vagrant", "ssh", "-c", "cat /vagrant/foo") 32 | expect(result.exit_code).to eql(0) 33 | expect(result.stdout).to match(/hello$/) 34 | 35 | status("Test: doesn't mount a disabled folder") 36 | result = execute("vagrant", "ssh", "-c", "test -d /foo") 37 | expect(result.exit_code).to eql(1) 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/acceptance/provisioner/chef_solo_spec.rb: -------------------------------------------------------------------------------- 1 | shared_examples "provider/provisioner/chef-solo" do |provider, options| 2 | box = options[:box_chef] || options[:box] 3 | if !box 4 | raise ArgumentError, 5 | "box_basic option must be specified for provider: #{provider}" 6 | end 7 | 8 | include_context "acceptance" 9 | 10 | before do 11 | environment.skeleton("provisioner_chef_solo") 12 | 13 | vagrantfile = environment.workdir.join('Vagrantfile') 14 | # TODO: Can we just shell out to something? 15 | new_vagrantfile = "Vagrant.require_plugin('docker-provider')\n#{vagrantfile.read}" 16 | vagrantfile.open('w') { |f| f.puts(new_vagrantfile) } 17 | 18 | assert_execute("vagrant", "box", "add", "box", box) 19 | assert_execute("vagrant", "up", "--provider=#{provider}") 20 | end 21 | 22 | after do 23 | assert_execute("vagrant", "destroy", "--force") 24 | end 25 | 26 | it "provisions with chef-solo" do 27 | status("Test: basic cookbooks and recipes") 28 | result = execute("vagrant", "ssh", "-c", "cat /vagrant-chef-basic") 29 | expect(result).to exit_with(0) 30 | expect(result.stdout).to match(/basic$/) 31 | 32 | status("Test: works with roles") 33 | result = execute("vagrant", "ssh", "-c", "cat /vagrant-chef-basic-roles") 34 | expect(result).to exit_with(0) 35 | expect(result.stdout).to match(/basic-roles$/) 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/acceptance/provisioner/puppet_spec.rb: -------------------------------------------------------------------------------- 1 | shared_examples "provider/provisioner/puppet" do |provider, options| 2 | box = options[:box_puppet] || options[:box] 3 | if !box 4 | raise ArgumentError, 5 | "box_basic option must be specified for provider: #{provider}" 6 | end 7 | 8 | include_context "acceptance" 9 | 10 | before do 11 | environment.skeleton("provisioner_puppet") 12 | 13 | vagrantfile = environment.workdir.join('Vagrantfile') 14 | # TODO: Can we just shell out to something? 15 | new_vagrantfile = "Vagrant.require_plugin('docker-provider')\n#{vagrantfile.read}" 16 | vagrantfile.open('w') { |f| f.puts(new_vagrantfile) } 17 | 18 | assert_execute("vagrant", "box", "add", "box", box) 19 | assert_execute("vagrant", "up", "--provider=#{provider}") 20 | end 21 | 22 | after do 23 | assert_execute("vagrant", "destroy", "--force") 24 | end 25 | 26 | it "provisions with puppet" do 27 | status("Test: basic manifests") 28 | result = execute("vagrant", "ssh", "-c", "cat /vagrant-puppet-basic") 29 | expect(result).to exit_with(0) 30 | expect(result.stdout).to match(/basic$/) 31 | 32 | status("Test: basic modules") 33 | result = execute("vagrant", "ssh", "-c", "cat /vagrant-puppet-basic-modules") 34 | expect(result).to exit_with(0) 35 | expect(result.stdout).to match(/modules$/) 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/acceptance/provisioner/shell_spec.rb: -------------------------------------------------------------------------------- 1 | shared_examples "provider/provisioner/shell" do |provider, options| 2 | if !options[:box] 3 | raise ArgumentError, 4 | "box_basic option must be specified for provider: #{provider}" 5 | end 6 | 7 | include_context "acceptance" 8 | 9 | before do 10 | environment.skeleton("provisioner_shell") 11 | 12 | vagrantfile = environment.workdir.join('Vagrantfile') 13 | # TODO: Can we just shell out to something? 14 | new_vagrantfile = "Vagrant.require_plugin('docker-provider')\n#{vagrantfile.read}" 15 | vagrantfile.open('w') { |f| f.puts(new_vagrantfile) } 16 | 17 | assert_execute("vagrant", "box", "add", "box", options[:box]) 18 | assert_execute("vagrant", "up", "--provider=#{provider}") 19 | end 20 | 21 | after do 22 | assert_execute("vagrant", "destroy", "--force") 23 | end 24 | 25 | it "provisions with the shell script" do 26 | status("Test: inline script") 27 | result = execute("vagrant", "ssh", "-c", "cat /foo") 28 | expect(result).to exit_with(0) 29 | expect(result.stdout).to match(/foo\n$/) 30 | 31 | status("Test: script from path") 32 | result = execute("vagrant", "ssh", "-c", "cat /vagrant-path") 33 | expect(result).to exit_with(0) 34 | expect(result.stdout).to match(/bar\n$/) 35 | 36 | status("Test: script with args") 37 | result = execute("vagrant", "ssh", "-c", "cat /vagrant-args") 38 | expect(result).to exit_with(0) 39 | expect(result.stdout).to match(/hello\ntwo words\n$/) 40 | 41 | status("Test: privileged scripts") 42 | result = execute("vagrant", "ssh", "-c", "cat /tmp/vagrant-user-root") 43 | expect(result).to exit_with(0) 44 | expect(result.stdout).to match(/root$/) 45 | 46 | status("Test: non-privileged scripts") 47 | result = execute("vagrant", "ssh", "-c", "cat /tmp/vagrant-user") 48 | expect(result).to exit_with(0) 49 | expect(result.stdout).to_not match(/root$/) 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /spec/acceptance/synced_folder/nfs_spec.rb: -------------------------------------------------------------------------------- 1 | shared_examples "provider/synced_folder/nfs" do |provider, options| 2 | if !options[:box] 3 | raise ArgumentError, 4 | "box option must be specified for provider: #{provider}" 5 | end 6 | 7 | include_context "acceptance" 8 | 9 | before do 10 | environment.skeleton("synced_folder_nfs") 11 | 12 | vagrantfile = environment.workdir.join('Vagrantfile') 13 | new_vagrantfile = "Vagrant.require_plugin('docker-provider')\n#{vagrantfile.read}" 14 | new_vagrantfile.gsub!(/(config\.vm\.box = "box")/, "\\1\nconfig.vm.provider :docker do |docker|\ndocker.privileged = true\nend\n") 15 | new_vagrantfile.gsub!(/(, type: "nfs")/, '\1, mount_options: ["rw", "vers=3", "tcp", "nolock"]') 16 | vagrantfile.open('w') { |f| f.puts(new_vagrantfile) } 17 | 18 | assert_execute("vagrant", "box", "add", "box", options[:box]) 19 | assert_execute("vagrant", "up", "--provider=#{provider}") 20 | end 21 | 22 | after do 23 | assert_execute("vagrant", "destroy", "--force") 24 | end 25 | 26 | it "properly configures NFS" do 27 | status("Test: mounts the NFS folder") 28 | result = execute("vagrant", "ssh", "-c", "cat /vagrant-nfs/foo") 29 | expect(result).to exit_with(0) 30 | expect(result.stdout).to match(/hello$/) 31 | 32 | status("Test: doesn't mount a disabled folder") 33 | result = execute("vagrant", "ssh", "-c", "test -d /foo") 34 | expect(result.exit_code).to eql(1) 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'bundler/setup' 2 | 3 | Dir[File.dirname(__FILE__) + "/support/**/*.rb"].each { |f| require f } 4 | 5 | RSpec.configure do |config| 6 | config.treat_symbols_as_metadata_keys_with_true_values = true 7 | config.run_all_when_everything_filtered = true 8 | config.filter_run :focus 9 | 10 | # Run specs in random order to surface order dependencies. If you find an 11 | # order dependency and want to debug it, you can fix the order by providing 12 | # the seed, which is printed after each run. 13 | # --seed 1234 14 | config.order = 'random' 15 | 16 | config.include UnitExampleGroup, :type => :unit, :example_group => { 17 | :file_path => /\bspec\/unit\// 18 | } 19 | end 20 | -------------------------------------------------------------------------------- /spec/support/unit_example_group.rb: -------------------------------------------------------------------------------- 1 | module UnitExampleGroup 2 | def self.included(base) 3 | base.metadata[:type] = :unit 4 | base.before do 5 | Object.any_instance.stub(:system) { |*args, &block| 6 | UnitExampleGroup.prevent_system_calls(*args, &block) 7 | } 8 | Object.any_instance.stub(:`) { |*args, &block| 9 | UnitExampleGroup.prevent_system_calls(*args, &block) 10 | } 11 | Object.any_instance.stub(:exec) { |*args, &block| 12 | UnitExampleGroup.prevent_system_calls(*args, &block) 13 | } 14 | Object.any_instance.stub(:fork) { |*args, &block| 15 | UnitExampleGroup.prevent_system_calls(*args, &block) 16 | } 17 | Object.any_instance.stub(:spawn) { |*args, &block| 18 | UnitExampleGroup.prevent_system_calls(*args, &block) 19 | } 20 | require 'vagrant/util/subprocess' 21 | Vagrant::Util::Subprocess.stub(:execute) { |*args, &block| 22 | UnitExampleGroup.prevent_system_calls(*args, &block) 23 | } 24 | end 25 | end 26 | 27 | def self.prevent_system_calls(*args, &block) 28 | args.pop if args.last.is_a?(Hash) 29 | 30 | raise <<-MSG 31 | Somehow your code under test is trying to execute a command on your system, 32 | please stub it out or move your spec code to an acceptance spec. 33 | 34 | Block: #{block.inspect} 35 | Command: "#{args.join(' ')}" 36 | MSG 37 | end 38 | end 39 | 40 | -------------------------------------------------------------------------------- /spec/unit/driver_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | require 'docker-provider/driver' 4 | 5 | describe VagrantPlugins::DockerProvider::Driver do 6 | let(:cmd_executed) { @cmd } 7 | let(:cid) { 'side-1-song-10' } 8 | 9 | before do 10 | subject.stub(:execute) { |*args| @cmd = args.join(' ') } 11 | end 12 | 13 | describe '#create' do 14 | let(:params) { { 15 | image: 'jimi/hendrix:eletric-ladyland', 16 | cmd: ['play', 'voodoo-chile'], 17 | ports: '8080:80', 18 | volumes: '/host/path:guest/path', 19 | name: cid, 20 | hostname: 'jimi-hendrix', 21 | privileged: true 22 | } } 23 | 24 | before { subject.create(params) } 25 | 26 | it 'runs a detached docker image' do 27 | expect(cmd_executed).to match(/^docker run .+ -d .+ #{Regexp.escape params[:image]}/) 28 | end 29 | 30 | it 'sets container name' do 31 | expect(cmd_executed).to match(/-name #{Regexp.escape params[:name]}/) 32 | end 33 | 34 | it 'forwards ports' do 35 | expect(cmd_executed).to match(/-p #{params[:ports]} .+ #{Regexp.escape params[:image]}/) 36 | end 37 | 38 | it 'shares folders' do 39 | expect(cmd_executed).to match(/-v #{params[:volumes]} .+ #{Regexp.escape params[:image]}/) 40 | end 41 | 42 | it 'is able to run a privileged container' do 43 | expect(cmd_executed).to match(/-privileged .+ #{Regexp.escape params[:image]}/) 44 | end 45 | 46 | it 'sets the hostname if specified' do 47 | expect(cmd_executed).to match(/-h #{params[:hostname]} #{Regexp.escape params[:image]}/) 48 | end 49 | 50 | it 'executes the provided command' do 51 | expect(cmd_executed).to match(/#{Regexp.escape params[:image]} #{Regexp.escape params[:cmd].join(' ')}/) 52 | end 53 | end 54 | 55 | describe '#created?' do 56 | let(:result) { subject.created?(cid) } 57 | 58 | it 'performs the check on all containers list' do 59 | subject.created?(cid) 60 | expect(cmd_executed).to match(/docker ps \-a \-q/) 61 | end 62 | 63 | context 'when container exists' do 64 | before { subject.stub(execute: "foo\n#{cid}\nbar") } 65 | it { expect(result).to be_true } 66 | end 67 | 68 | context 'when container does not exist' do 69 | before { subject.stub(execute: "foo\n#{cid}extra\nbar") } 70 | it { expect(result).to be_false } 71 | end 72 | end 73 | 74 | describe '#running?' do 75 | let(:result) { subject.running?(cid) } 76 | 77 | it 'performs the check on the running containers list' do 78 | subject.running?(cid) 79 | expect(cmd_executed).to match(/docker ps \-q/) 80 | expect(cmd_executed).to_not include('-a') 81 | end 82 | 83 | context 'when container exists' do 84 | before { subject.stub(execute: "foo\n#{cid}\nbar") } 85 | it { expect(result).to be_true } 86 | end 87 | 88 | context 'when container does not exist' do 89 | before { subject.stub(execute: "foo\n#{cid}extra\nbar") } 90 | it { expect(result).to be_false } 91 | end 92 | end 93 | 94 | describe '#privileged?' do 95 | it 'identifies privileged containers' do 96 | subject.stub(inspect_container: {'HostConfig' => {"Privileged" => true}}) 97 | expect(subject).to be_privileged(cid) 98 | end 99 | 100 | it 'identifies unprivileged containers' do 101 | subject.stub(inspect_container: {'HostConfig' => {"Privileged" => false}}) 102 | expect(subject).to_not be_privileged(cid) 103 | end 104 | end 105 | 106 | describe '#start' do 107 | context 'when container is running' do 108 | before { subject.stub(running?: true) } 109 | 110 | it 'does not start the container' do 111 | subject.should_not_receive(:execute).with('docker', 'start', cid) 112 | subject.start(cid) 113 | end 114 | end 115 | 116 | context 'when container is not running' do 117 | before { subject.stub(running?: false) } 118 | 119 | it 'starts the container' do 120 | subject.should_receive(:execute).with('docker', 'start', cid) 121 | subject.start(cid) 122 | end 123 | end 124 | end 125 | 126 | describe '#stop' do 127 | context 'when container is running' do 128 | before { subject.stub(running?: true) } 129 | 130 | it 'stops the container' do 131 | subject.should_receive(:execute).with('docker', 'stop', '-t', '1', cid) 132 | subject.stop(cid) 133 | end 134 | end 135 | 136 | context 'when container is not running' do 137 | before { subject.stub(running?: false) } 138 | 139 | it 'does not stop container' do 140 | subject.should_not_receive(:execute).with('docker', 'stop', '-t', '1', cid) 141 | subject.stop(cid) 142 | end 143 | end 144 | end 145 | 146 | describe '#rm' do 147 | context 'when container has been created' do 148 | before { subject.stub(created?: true) } 149 | 150 | it 'removes the container' do 151 | subject.should_receive(:execute).with('docker', 'rm', '-v', cid) 152 | subject.rm(cid) 153 | end 154 | end 155 | 156 | context 'when container has not been created' do 157 | before { subject.stub(created?: false) } 158 | 159 | it 'does not attempt to remove the container' do 160 | subject.should_not_receive(:execute).with('docker', 'rm', '-v', cid) 161 | subject.rm(cid) 162 | end 163 | end 164 | end 165 | 166 | describe '#inspect_container' do 167 | let(:data) { '[{"json": "value"}]' } 168 | 169 | before { subject.stub(execute: data) } 170 | 171 | it 'inspects the container' do 172 | subject.should_receive(:execute).with('docker', 'inspect', cid) 173 | subject.inspect_container(cid) 174 | end 175 | 176 | it 'parses the json output' do 177 | expect(subject.inspect_container(cid)).to eq('json' => 'value') 178 | end 179 | end 180 | 181 | describe '#all_containers' do 182 | let(:containers) { "container1\ncontainer2" } 183 | 184 | before { subject.stub(execute: containers) } 185 | 186 | it 'returns an array of all known containers' do 187 | subject.should_receive(:execute).with('docker', 'ps', '-a', '-q', '-notrunc') 188 | expect(subject.all_containers).to eq(['container1', 'container2']) 189 | end 190 | end 191 | 192 | describe '#docker_bridge_ip' do 193 | let(:containers) { " inet 123.456.789.012/16 " } 194 | 195 | before { subject.stub(execute: containers) } 196 | 197 | it 'returns an array of all known containers' do 198 | subject.should_receive(:execute).with('/sbin/ip', '-4', 'addr', 'show', 'scope', 'global', 'docker0') 199 | expect(subject.docker_bridge_ip).to eq('123.456.789.012') 200 | end 201 | end 202 | end 203 | -------------------------------------------------------------------------------- /vagrant-spec.config.rb: -------------------------------------------------------------------------------- 1 | Vagrant::Spec::Acceptance.configure do |c| 2 | c.component_paths << File.expand_path("../spec/acceptance", __FILE__) 3 | 4 | c.provider "docker", 5 | # wget http://bit.ly/vagrant-docker-precise -O precise.box 6 | box: "#{File.expand_path("../", __FILE__)}/precise.box", 7 | features: ['!suspend'] 8 | end 9 | --------------------------------------------------------------------------------