providers/docker: Import code from https://github.com/fgrehm/docker-provider with some initial tweaks
This commit is contained in:
parent
efc1122c42
commit
5a60e568ce
|
@ -0,0 +1,206 @@
|
||||||
|
# docker-provider
|
||||||
|
|
||||||
|
[![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/)
|
||||||
|
|
||||||
|
A [Docker](http://www.docker.io/) provider for [Vagrant](http://www.vagrantup.com/)
|
||||||
|
1.4+.
|
||||||
|
|
||||||
|
|
||||||
|
## Warning
|
||||||
|
|
||||||
|
This is experimental, expect things to break.
|
||||||
|
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
* Vagrant 1.4+
|
||||||
|
* Docker 0.7.0+
|
||||||
|
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
* Support for Vagrant's `up`, `destroy`, `halt`, `reload` and `ssh` commands
|
||||||
|
* Port forwarding
|
||||||
|
* Synced / shared folders support
|
||||||
|
* Set container hostnames from Vagrantfiles
|
||||||
|
* Provision Docker containers with any built-in Vagrant provisioner (as long as the container has a SSH server running)
|
||||||
|
|
||||||
|
You can see the plugin in action by watching the following asciicasts I published
|
||||||
|
prior to releasing 0.0.1:
|
||||||
|
|
||||||
|
* http://asciinema.org/a/6162
|
||||||
|
* http://asciinema.org/a/6177
|
||||||
|
|
||||||
|
|
||||||
|
## Getting started
|
||||||
|
|
||||||
|
If you are on a Mac / Windows machine, please fire up a x64 Linux VM with Docker +
|
||||||
|
Vagrant 1.4+ installed or use [this Vagrantfile](https://gist.github.com/fgrehm/fc48fb51ec7df64439e4)
|
||||||
|
and follow the instructions from within the VM.
|
||||||
|
|
||||||
|
_It is likely that the plugin works with [boot2docker](http://boot2docker.github.io/)
|
||||||
|
but I personally haven't tried that yet. If you are able to give it a go please
|
||||||
|
[let me know](https://github.com/fgrehm/docker-provider/issues/new)._
|
||||||
|
|
||||||
|
### Initial setup
|
||||||
|
|
||||||
|
_If you are trying things out from a Vagrant VM using the `Vagrantfile` gisted
|
||||||
|
above, you can skip to the next section_
|
||||||
|
|
||||||
|
The plugin requires Docker's executable to be available on current user's `PATH`
|
||||||
|
and that the current user has been added to the `docker` group since we are not
|
||||||
|
using `sudo` when interacting with Docker's CLI. For more information on setting
|
||||||
|
this up please check [this page](http://docs.docker.io/en/latest/installation/ubuntulinux/#giving-non-root-access).
|
||||||
|
|
||||||
|
### `vagrant up`
|
||||||
|
|
||||||
|
On its current state, the plugin is not "user friendly" and won't provide any kind
|
||||||
|
of feedback about the process of downloading Docker images, so before you add a
|
||||||
|
`docker-provider` [base box](http://docs.vagrantup.com/v2/boxes.html) it is recommended
|
||||||
|
that you `docker pull` the associated base box images prior to spinning up `docker-provider`
|
||||||
|
containers (otherwise you'll be staring at a blinking cursor without any progress
|
||||||
|
information for a while).
|
||||||
|
|
||||||
|
Assuming you have Vagrant 1.4+ and Docker 0.7.0+ installed just sing that same
|
||||||
|
old song:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
vagrant plugin install docker-provider
|
||||||
|
docker pull fgrehm/vagrant-ubuntu:precise
|
||||||
|
vagrant box add precise64 http://bit.ly/vagrant-docker-precise
|
||||||
|
vagrant init precise64
|
||||||
|
vagrant up --provider=docker
|
||||||
|
```
|
||||||
|
|
||||||
|
Under the hood, that base box will [configure](#configuration) `docker-provider`
|
||||||
|
to use the [`fgrehm/vagrant-ubuntu:precise`](https://index.docker.io/u/fgrehm/vagrant-ubuntu/)
|
||||||
|
image that approximates a standard Vagrant box (`vagrant` user, default SSH key,
|
||||||
|
etc.) and you should be good to go.
|
||||||
|
|
||||||
|
|
||||||
|
## Using custom images
|
||||||
|
|
||||||
|
If you want to use a custom Docker image without creating a Vagrant base box,
|
||||||
|
you can use a "dummy" box and configure things from your `Vagrantfile` like
|
||||||
|
in [vagrant-digitalocean](https://github.com/smdahlen/vagrant-digitalocean#configure)
|
||||||
|
or [vagrant-aws](https://github.com/mitchellh/vagrant-aws#quick-start):
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
Vagrant.configure("2") do |config|
|
||||||
|
config.vm.box = "dummy"
|
||||||
|
config.vm.box_url = "http://bit.ly/vagrant-docker-dummy"
|
||||||
|
config.vm.provider :docker do |docker|
|
||||||
|
docker.image = "your/image:tag"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
This provider exposes a few provider-specific configuration options
|
||||||
|
that are passed on to `docker run` under the hood when the container
|
||||||
|
is being created:
|
||||||
|
|
||||||
|
* `image` - Docker image to run (required)
|
||||||
|
* `privileged` - Give extended privileges to the container (defaults to false)
|
||||||
|
* `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`)
|
||||||
|
* `ports` - An array of strings that makes up for the mapped network ports
|
||||||
|
* `volumes` - An array of strings that makes up for the data volumes used by the container
|
||||||
|
|
||||||
|
These can be set like typical provider-specific configuration:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
Vagrant.configure("2") do |config|
|
||||||
|
# ... other stuff
|
||||||
|
|
||||||
|
config.vm.provider :docker do |docker|
|
||||||
|
docker.image = 'fgrehm/vagrant-ubuntu-dind:precise'
|
||||||
|
docker.privileged = true
|
||||||
|
docker.cmd = ['/dind', '/sbin/init']
|
||||||
|
|
||||||
|
docker.ports << '1234:22'
|
||||||
|
docker.volumes << '/var/lib/docker'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Networks
|
||||||
|
|
||||||
|
Networking features in the form of `config.vm.network` are not supported with
|
||||||
|
`docker-provider` apart from [forwarded ports]().
|
||||||
|
If any of [`:private_network`](http://docs.vagrantup.com/v2/networking/private_network.html)
|
||||||
|
or [`:public_network`](http://docs.vagrantup.com/v2/networking/public_network.html)
|
||||||
|
are specified, Vagrant **won't** emit a warning.
|
||||||
|
|
||||||
|
The same applies to changes on forwarded ports after the container has been
|
||||||
|
created, Vagrant **won't** emit a warning to let you know that the ports specified
|
||||||
|
on your `Vagrantfile` differs from what has been passed on to `docker run` when
|
||||||
|
creating the container.
|
||||||
|
|
||||||
|
_At some point the plugin will emit warnings on the scenarios described above, but
|
||||||
|
not on its current state. Pull Requests are encouraged ;)_
|
||||||
|
|
||||||
|
|
||||||
|
## Synced Folders
|
||||||
|
|
||||||
|
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)
|
||||||
|
but as with forwarded ports, you won't be able to change them after the container
|
||||||
|
has been created. [NFS](http://docs.vagrantup.com/v2/synced-folders/nfs.html)
|
||||||
|
synced folders are also supported (as long as you set the `privileged`
|
||||||
|
[config](#configuration) to true so that `docker-provider` can mount it on the
|
||||||
|
guest container) and are capable of being reconfigured between `vagrant reload`s
|
||||||
|
(different from Docker volumes).
|
||||||
|
|
||||||
|
This is good enough for all built-in Vagrant provisioners (shell,
|
||||||
|
chef, and puppet) to work!
|
||||||
|
|
||||||
|
_At some point the plugin will emit warnings when the configured `Vagrantfile`
|
||||||
|
synced folders / volumes differs from the ones used upon the container creation,
|
||||||
|
but not on its current state. Pull Requests are encouraged ;)_
|
||||||
|
|
||||||
|
|
||||||
|
## Box format
|
||||||
|
|
||||||
|
Every provider in Vagrant must introduce a custom box format. This provider introduces
|
||||||
|
`docker` boxes and you can view some examples in the [`boxes`](boxes) directory.
|
||||||
|
That directory also contains instructions on how to build them.
|
||||||
|
|
||||||
|
The box format is basically just the required `metadata.json` file along with a
|
||||||
|
`Vagrantfile` that does default settings for the provider-specific configuration
|
||||||
|
for this provider.
|
||||||
|
|
||||||
|
|
||||||
|
## Available base boxes
|
||||||
|
|
||||||
|
| LINK | DESCRIPTION |
|
||||||
|
| --- | --- |
|
||||||
|
| http://bit.ly/vagrant-docker-precise | Ubuntu 12.04 Precise x86_64 with Puppet and Chef preinstalled and configured to run `/sbin/init` |
|
||||||
|
| 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) |
|
||||||
|
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
As explained on the [networks](#networks) and [synced folder](#synced-folders)
|
||||||
|
sections above, there are some "gotchas" when using the plugin that you need to have
|
||||||
|
in mind before you start to pull your hair out.
|
||||||
|
|
||||||
|
For instance, forwarded ports, synced folders and containers' hostnames will not be
|
||||||
|
reconfigured on `vagrant reload`s if they have changed and the plugin **_will not
|
||||||
|
give you any kind of warning or message_**. As an example, if you change your Puppet
|
||||||
|
manifests / Chef cookbooks paths (which are shared / synced folders under the hood),
|
||||||
|
**_you'll need to start from scratch_** (unless you make them NFS shared folders).
|
||||||
|
This is due to a limitation in Docker itself as we can't change those parameters
|
||||||
|
after the container has been created.
|
||||||
|
|
||||||
|
Forwarded ports automatic collision handling is **_not supported as well_**.
|
||||||
|
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
1. Fork it
|
||||||
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
||||||
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
||||||
|
4. Push to the branch (`git push origin my-new-feature`)
|
||||||
|
5. Create new Pull Request
|
|
@ -0,0 +1,175 @@
|
||||||
|
module VagrantPlugins
|
||||||
|
module DockerProvider
|
||||||
|
module Action
|
||||||
|
# Shortcuts
|
||||||
|
Builtin = Vagrant::Action::Builtin
|
||||||
|
Builder = Vagrant::Action::Builder
|
||||||
|
|
||||||
|
# This action brings the "machine" up from nothing, including creating the
|
||||||
|
# container, configuring metadata, and booting.
|
||||||
|
def self.action_up
|
||||||
|
Builder.new.tap do |b|
|
||||||
|
b.use Builtin::ConfigValidate
|
||||||
|
b.use Builtin::Call, Created do |env, b2|
|
||||||
|
if !env[:result]
|
||||||
|
b2.use Builtin::HandleBoxUrl
|
||||||
|
# TODO: Find out where this fits into the process
|
||||||
|
# b2.use Builtin::EnvSet, :port_collision_repair => true
|
||||||
|
# b2.use Builtin::HandleForwardedPortCollisions
|
||||||
|
b2.use Builtin::Provision
|
||||||
|
b2.use PrepareNFSValidIds
|
||||||
|
b2.use Builtin::SyncedFolderCleanup
|
||||||
|
b2.use Builtin::SyncedFolders
|
||||||
|
b2.use PrepareNFSSettings
|
||||||
|
b2.use ForwardPorts
|
||||||
|
# This will actually create and start, but that's fine
|
||||||
|
b2.use Create
|
||||||
|
b2.use action_boot
|
||||||
|
else
|
||||||
|
b2.use PrepareNFSValidIds
|
||||||
|
b2.use Builtin::SyncedFolderCleanup
|
||||||
|
b2.use Builtin::SyncedFolders
|
||||||
|
b2.use PrepareNFSSettings
|
||||||
|
b2.use action_start
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# This action just runs the provisioners on the machine.
|
||||||
|
def self.action_provision
|
||||||
|
Builder.new.tap do |b|
|
||||||
|
b.use Builtin::ConfigValidate
|
||||||
|
b.use Builtin::Call, Created do |env1, b2|
|
||||||
|
if !env1[:result]
|
||||||
|
b2.use Message, :not_created
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
|
b2.use Builtin::Call, IsRunning do |env2, b3|
|
||||||
|
if !env2[:result]
|
||||||
|
b3.use Message, :not_running
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
|
b3.use Builtin::Provision
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# This is the action that is primarily responsible for halting
|
||||||
|
# the virtual machine, gracefully or by force.
|
||||||
|
def self.action_halt
|
||||||
|
Builder.new.tap do |b|
|
||||||
|
b.use Builtin::Call, Created do |env, b2|
|
||||||
|
if env[:result]
|
||||||
|
b2.use Builtin::Call, Builtin::GracefulHalt, :stopped, :running do |env2, b3|
|
||||||
|
if !env2[:result]
|
||||||
|
b3.use Stop
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
b2.use Message, :not_created
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# This action is responsible for reloading the machine, which
|
||||||
|
# brings it down, sucks in new configuration, and brings the
|
||||||
|
# machine back up with the new configuration.
|
||||||
|
def self.action_reload
|
||||||
|
Builder.new.tap do |b|
|
||||||
|
b.use Builtin::Call, Created do |env1, b2|
|
||||||
|
if !env1[:result]
|
||||||
|
b2.use Message, :not_created
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
|
b2.use Builtin::ConfigValidate
|
||||||
|
b2.use action_halt
|
||||||
|
b2.use action_start
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# This is the action that is primarily responsible for completely
|
||||||
|
# freeing the resources of the underlying virtual machine.
|
||||||
|
def self.action_destroy
|
||||||
|
Builder.new.tap do |b|
|
||||||
|
b.use Builtin::Call, Created do |env1, b2|
|
||||||
|
if !env1[:result]
|
||||||
|
b2.use Message, :not_created
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
|
b2.use Builtin::Call, Builtin::DestroyConfirm do |env2, b3|
|
||||||
|
if env2[:result]
|
||||||
|
b3.use Builtin::ConfigValidate
|
||||||
|
b3.use Builtin::EnvSet, :force_halt => true
|
||||||
|
b3.use action_halt
|
||||||
|
b3.use Destroy
|
||||||
|
b3.use Builtin::ProvisionerCleanup
|
||||||
|
else
|
||||||
|
b3.use Message, :will_not_destroy
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# This is the action that will exec into an SSH shell.
|
||||||
|
def self.action_ssh
|
||||||
|
Builder.new.tap do |b|
|
||||||
|
b.use CheckRunning
|
||||||
|
b.use Builtin::SSHExec
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# This is the action that will run a single SSH command.
|
||||||
|
def self.action_ssh_run
|
||||||
|
Builder.new.tap do |b|
|
||||||
|
b.use CheckRunning
|
||||||
|
b.use Builtin::SSHRun
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.action_start
|
||||||
|
Builder.new.tap do |b|
|
||||||
|
b.use Builtin::ConfigValidate
|
||||||
|
b.use Builtin::Call, IsRunning do |env, b2|
|
||||||
|
# If the container is running, then our work here is done, exit
|
||||||
|
next if env[:result]
|
||||||
|
|
||||||
|
b2.use Builtin::Provision
|
||||||
|
b2.use Message, :starting
|
||||||
|
b2.use action_boot
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.action_boot
|
||||||
|
Builder.new.tap do |b|
|
||||||
|
# TODO: b.use Builtin::SetHostname
|
||||||
|
b.use Start
|
||||||
|
b.use Builtin::WaitForCommunicator
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# The autoload farm
|
||||||
|
action_root = Pathname.new(File.expand_path("../action", __FILE__))
|
||||||
|
autoload :CheckRunning, action_root.join("check_running")
|
||||||
|
autoload :Created, action_root.join("created")
|
||||||
|
autoload :Create, action_root.join("create")
|
||||||
|
autoload :Destroy, action_root.join("destroy")
|
||||||
|
autoload :ForwardPorts, action_root.join("forward_ports")
|
||||||
|
autoload :Stop, action_root.join("stop")
|
||||||
|
autoload :Message, action_root.join("message")
|
||||||
|
autoload :PrepareNFSValidIds, action_root.join("prepare_nfs_valid_ids")
|
||||||
|
autoload :PrepareNFSSettings, action_root.join("prepare_nfs_settings")
|
||||||
|
autoload :IsRunning, action_root.join("is_running")
|
||||||
|
autoload :Start, action_root.join("start")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,25 @@
|
||||||
|
module VagrantPlugins
|
||||||
|
module DockerProvider
|
||||||
|
module Action
|
||||||
|
class CheckRunning
|
||||||
|
def initialize(app, env)
|
||||||
|
@app = app
|
||||||
|
end
|
||||||
|
|
||||||
|
def call(env)
|
||||||
|
if env[:machine].state.id == :not_created
|
||||||
|
raise Vagrant::Errors::VMNotCreatedError
|
||||||
|
end
|
||||||
|
|
||||||
|
if env[:machine].state.id == :stopped
|
||||||
|
raise Vagrant::Errors::VMNotRunningError
|
||||||
|
end
|
||||||
|
|
||||||
|
# Call the next if we have one (but we shouldn't, since this
|
||||||
|
# middleware is built to run with the Call-type middlewares)
|
||||||
|
@app.call(env)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,59 @@
|
||||||
|
module VagrantPlugins
|
||||||
|
module DockerProvider
|
||||||
|
module Action
|
||||||
|
class Create
|
||||||
|
def initialize(app, env)
|
||||||
|
@app = app
|
||||||
|
@@mutex ||= Mutex.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def call(env)
|
||||||
|
@env = env
|
||||||
|
@machine = env[:machine]
|
||||||
|
@provider_config = @machine.provider_config
|
||||||
|
@machine_config = @machine.config
|
||||||
|
@driver = @machine.provider.driver
|
||||||
|
|
||||||
|
guard_cmd_configured!
|
||||||
|
|
||||||
|
cid = ''
|
||||||
|
@@mutex.synchronize do
|
||||||
|
cid = @driver.create(create_params)
|
||||||
|
end
|
||||||
|
|
||||||
|
@machine.id = cid
|
||||||
|
@app.call(env)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_params
|
||||||
|
container_name = "#{@env[:root_path].basename.to_s}_#{@machine.name}"
|
||||||
|
container_name.gsub!(/[^-a-z0-9_]/i, "")
|
||||||
|
container_name << "_#{Time.now.to_i}"
|
||||||
|
|
||||||
|
{
|
||||||
|
image: @provider_config.image,
|
||||||
|
cmd: @provider_config.cmd,
|
||||||
|
ports: forwarded_ports,
|
||||||
|
name: container_name,
|
||||||
|
hostname: @machine_config.vm.hostname,
|
||||||
|
volumes: @provider_config.volumes,
|
||||||
|
privileged: @provider_config.privileged
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def forwarded_ports
|
||||||
|
@env[:forwarded_ports].map do |fp|
|
||||||
|
# TODO: Support for the protocol argument
|
||||||
|
"#{fp[:host]}:#{fp[:guest]}"
|
||||||
|
end.compact
|
||||||
|
end
|
||||||
|
|
||||||
|
def guard_cmd_configured!
|
||||||
|
if ! @provider_config.image
|
||||||
|
raise Errors::ImageNotConfiguredError, name: @machine.name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,18 @@
|
||||||
|
module VagrantPlugins
|
||||||
|
module DockerProvider
|
||||||
|
module Action
|
||||||
|
class Created
|
||||||
|
def initialize(app, env)
|
||||||
|
@app = app
|
||||||
|
end
|
||||||
|
|
||||||
|
def call(env)
|
||||||
|
machine = env[:machine]
|
||||||
|
driver = machine.provider.driver
|
||||||
|
env[:result] = machine.id && driver.created?(machine.id)
|
||||||
|
@app.call(env)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,24 @@
|
||||||
|
module VagrantPlugins
|
||||||
|
module DockerProvider
|
||||||
|
module Action
|
||||||
|
class Destroy
|
||||||
|
def initialize(app, env)
|
||||||
|
@app = app
|
||||||
|
end
|
||||||
|
|
||||||
|
def call(env)
|
||||||
|
env[:ui].info I18n.t("vagrant.actions.vm.destroy.destroying")
|
||||||
|
|
||||||
|
machine = env[:machine]
|
||||||
|
config = machine.provider_config
|
||||||
|
driver = machine.provider.driver
|
||||||
|
|
||||||
|
driver.rm(machine.id)
|
||||||
|
machine.id = nil
|
||||||
|
|
||||||
|
@app.call env
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,54 @@
|
||||||
|
module VagrantPlugins
|
||||||
|
module DockerProvider
|
||||||
|
module Action
|
||||||
|
class ForwardPorts
|
||||||
|
def initialize(app, env)
|
||||||
|
@app = app
|
||||||
|
end
|
||||||
|
|
||||||
|
def call(env)
|
||||||
|
@env = env
|
||||||
|
|
||||||
|
env[:forwarded_ports] = compile_forwarded_ports(env[:machine].config)
|
||||||
|
|
||||||
|
if env[:forwarded_ports].any?
|
||||||
|
env[:ui].info I18n.t("vagrant.actions.vm.forward_ports.forwarding")
|
||||||
|
inform_forwarded_ports(env[:forwarded_ports])
|
||||||
|
end
|
||||||
|
|
||||||
|
# FIXME: Check whether the container has already been created with
|
||||||
|
# different exposed ports and let the user know about it
|
||||||
|
|
||||||
|
@app.call env
|
||||||
|
end
|
||||||
|
|
||||||
|
def inform_forwarded_ports(ports)
|
||||||
|
ports.each do |fp|
|
||||||
|
message_attributes = {
|
||||||
|
:adapter => 'eth0',
|
||||||
|
:guest_port => fp[:guest],
|
||||||
|
:host_port => fp[:host]
|
||||||
|
}
|
||||||
|
|
||||||
|
@env[:ui].info(I18n.t("vagrant.actions.vm.forward_ports.forwarding_entry",
|
||||||
|
message_attributes))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def compile_forwarded_ports(config)
|
||||||
|
mappings = {}
|
||||||
|
|
||||||
|
config.vm.networks.each do |type, options|
|
||||||
|
if type == :forwarded_port && options[:id] != 'ssh'
|
||||||
|
mappings[options[:host]] = options
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
mappings.values
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,20 @@
|
||||||
|
module VagrantPlugins
|
||||||
|
module DockerProvider
|
||||||
|
module Action
|
||||||
|
class IsRunning
|
||||||
|
def initialize(app, env)
|
||||||
|
@app = app
|
||||||
|
end
|
||||||
|
|
||||||
|
def call(env)
|
||||||
|
machine = env[:machine]
|
||||||
|
driver = machine.provider.driver
|
||||||
|
|
||||||
|
env[:result] = driver.running?(machine.id)
|
||||||
|
|
||||||
|
@app.call(env)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,23 @@
|
||||||
|
module VagrantPlugins
|
||||||
|
module DockerProvider
|
||||||
|
module Action
|
||||||
|
# XXX: Is this really needed? Should we contribute this back to Vagrant's core?
|
||||||
|
class Message
|
||||||
|
def initialize(app, env, msg_key, type = :info)
|
||||||
|
@app = app
|
||||||
|
@msg_key = msg_key
|
||||||
|
@type = type
|
||||||
|
end
|
||||||
|
|
||||||
|
def call(env)
|
||||||
|
machine = env[:machine]
|
||||||
|
message = I18n.t("docker_provider.messages.#{@msg_key}", name: machine.name)
|
||||||
|
|
||||||
|
env[:ui].send @type, message
|
||||||
|
|
||||||
|
@app.call env
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,59 @@
|
||||||
|
require_relative '../errors'
|
||||||
|
|
||||||
|
module VagrantPlugins
|
||||||
|
module DockerProvider
|
||||||
|
module Action
|
||||||
|
class PrepareNFSSettings
|
||||||
|
include Vagrant::Util::Retryable
|
||||||
|
|
||||||
|
def initialize(app, env)
|
||||||
|
@app = app
|
||||||
|
@logger = Log4r::Logger.new("vagrant::action::vm::nfs")
|
||||||
|
end
|
||||||
|
|
||||||
|
def call(env)
|
||||||
|
@machine = env[:machine]
|
||||||
|
|
||||||
|
@app.call(env)
|
||||||
|
|
||||||
|
if using_nfs? && !privileged_container?
|
||||||
|
raise Errors::NfsWithoutPrivilegedError
|
||||||
|
end
|
||||||
|
|
||||||
|
if using_nfs?
|
||||||
|
@logger.info("Using NFS, preparing NFS settings by reading host IP and machine IP")
|
||||||
|
add_ips_to_env!(env)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# We're using NFS if we have any synced folder with NFS configured. If
|
||||||
|
# we are not using NFS we don't need to do the extra work to
|
||||||
|
# populate these fields in the environment.
|
||||||
|
def using_nfs?
|
||||||
|
@machine.config.vm.synced_folders.any? { |_, opts| opts[:type] == :nfs }
|
||||||
|
end
|
||||||
|
|
||||||
|
def privileged_container?
|
||||||
|
@machine.provider.driver.privileged?(@machine.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Extracts the proper host and guest IPs for NFS mounts and stores them
|
||||||
|
# in the environment for the SyncedFolder action to use them in
|
||||||
|
# mounting.
|
||||||
|
#
|
||||||
|
# The ! indicates that this method modifies its argument.
|
||||||
|
def add_ips_to_env!(env)
|
||||||
|
provider = env[:machine].provider
|
||||||
|
|
||||||
|
host_ip = provider.driver.docker_bridge_ip
|
||||||
|
machine_ip = provider.ssh_info[:host]
|
||||||
|
|
||||||
|
raise Vagrant::Errors::NFSNoHostonlyNetwork if !host_ip || !machine_ip
|
||||||
|
|
||||||
|
env[:nfs_host_ip] = host_ip
|
||||||
|
env[:nfs_machine_ip] = machine_ip
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,19 @@
|
||||||
|
module VagrantPlugins
|
||||||
|
module DockerProvider
|
||||||
|
module Action
|
||||||
|
class PrepareNFSValidIds
|
||||||
|
def initialize(app, env)
|
||||||
|
@app = app
|
||||||
|
@logger = Log4r::Logger.new("vagrant::action::vm::nfs")
|
||||||
|
end
|
||||||
|
|
||||||
|
def call(env)
|
||||||
|
machine = env[:machine]
|
||||||
|
env[:nfs_valid_ids] = machine.provider.driver.all_containers
|
||||||
|
|
||||||
|
@app.call(env)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,18 @@
|
||||||
|
module VagrantPlugins
|
||||||
|
module DockerProvider
|
||||||
|
module Action
|
||||||
|
class Start
|
||||||
|
def initialize(app, env)
|
||||||
|
@app = app
|
||||||
|
end
|
||||||
|
|
||||||
|
def call(env)
|
||||||
|
machine = env[:machine]
|
||||||
|
driver = machine.provider.driver
|
||||||
|
driver.start(machine.id)
|
||||||
|
@app.call(env)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,21 @@
|
||||||
|
module VagrantPlugins
|
||||||
|
module DockerProvider
|
||||||
|
module Action
|
||||||
|
class Stop
|
||||||
|
def initialize(app, env)
|
||||||
|
@app = app
|
||||||
|
end
|
||||||
|
|
||||||
|
def call(env)
|
||||||
|
machine = env[:machine]
|
||||||
|
driver = machine.provider.driver
|
||||||
|
if driver.running?(machine.id)
|
||||||
|
env[:ui].info I18n.t("docker_provider.messages.stopping")
|
||||||
|
driver.stop(machine.id)
|
||||||
|
end
|
||||||
|
@app.call(env)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,35 @@
|
||||||
|
module VagrantPlugins
|
||||||
|
module DockerProvider
|
||||||
|
class Config < Vagrant.plugin("2", :config)
|
||||||
|
attr_accessor :image, :cmd, :ports, :volumes, :privileged
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
@image = nil
|
||||||
|
@cmd = UNSET_VALUE
|
||||||
|
@ports = []
|
||||||
|
@privileged = UNSET_VALUE
|
||||||
|
@volumes = []
|
||||||
|
end
|
||||||
|
|
||||||
|
def finalize!
|
||||||
|
@cmd = [] if @cmd == UNSET_VALUE
|
||||||
|
@privileged = false if @privileged == UNSET_VALUE
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate(machine)
|
||||||
|
errors = _detected_errors
|
||||||
|
|
||||||
|
# TODO: Detect if base image has a CMD / ENTRYPOINT set before erroring out
|
||||||
|
errors << I18n.t("docker_provider.errors.config.cmd_not_set") if @cmd == UNSET_VALUE
|
||||||
|
|
||||||
|
{ "docker-provider" => errors }
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def using_nfs?(machine)
|
||||||
|
machine.config.vm.synced_folders.any? { |_, opts| opts[:type] == :nfs }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,138 @@
|
||||||
|
require "vagrant/util/busy"
|
||||||
|
require "vagrant/util/subprocess"
|
||||||
|
require "vagrant/util/retryable"
|
||||||
|
|
||||||
|
require 'log4r'
|
||||||
|
require 'json'
|
||||||
|
|
||||||
|
module VagrantPlugins
|
||||||
|
module DockerProvider
|
||||||
|
class Driver
|
||||||
|
include Vagrant::Util::Retryable
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
@logger = Log4r::Logger.new("vagrant::docker::driver")
|
||||||
|
end
|
||||||
|
|
||||||
|
def create(params)
|
||||||
|
image = params.fetch(:image)
|
||||||
|
ports = Array(params[:ports])
|
||||||
|
volumes = Array(params[:volumes])
|
||||||
|
name = params.fetch(:name)
|
||||||
|
cmd = Array(params.fetch(:cmd))
|
||||||
|
|
||||||
|
run_cmd = %W(docker run -name #{name} -d)
|
||||||
|
run_cmd += ports.map { |p| ['-p', p.to_s] }
|
||||||
|
run_cmd += volumes.map { |v| ['-v', v.to_s] }
|
||||||
|
run_cmd += %W(-privileged) if params[:privileged]
|
||||||
|
run_cmd += %W(-h #{params[:hostname]}) if params[:hostname]
|
||||||
|
run_cmd += [image, cmd]
|
||||||
|
|
||||||
|
retryable(tries: 10, sleep: 1) do
|
||||||
|
execute(*run_cmd.flatten).chomp
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def state(cid)
|
||||||
|
case
|
||||||
|
when running?(cid)
|
||||||
|
:running
|
||||||
|
when created?(cid)
|
||||||
|
:stopped
|
||||||
|
else
|
||||||
|
:not_created
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def created?(cid)
|
||||||
|
result = execute('docker', 'ps', '-a', '-q', '-notrunc').to_s
|
||||||
|
result =~ /^#{Regexp.escape cid}$/
|
||||||
|
end
|
||||||
|
|
||||||
|
def running?(cid)
|
||||||
|
result = execute('docker', 'ps', '-q', '-notrunc')
|
||||||
|
result =~ /^#{Regexp.escape cid}$/m
|
||||||
|
end
|
||||||
|
|
||||||
|
def privileged?(cid)
|
||||||
|
inspect_container(cid)['HostConfig']['Privileged']
|
||||||
|
end
|
||||||
|
|
||||||
|
def start(cid)
|
||||||
|
unless running?(cid)
|
||||||
|
execute('docker', 'start', cid)
|
||||||
|
# This resets the cached information we have around, allowing `vagrant reload`s
|
||||||
|
# to work properly
|
||||||
|
# TODO: Add spec to verify this behavior
|
||||||
|
@data = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def stop(cid)
|
||||||
|
if running?(cid)
|
||||||
|
execute('docker', 'stop', '-t', '1', cid)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def rm(cid)
|
||||||
|
if created?(cid)
|
||||||
|
execute('docker', 'rm', '-v', cid)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def inspect_container(cid)
|
||||||
|
# DISCUSS: Is there a chance that this json will change after the container
|
||||||
|
# has been brought up?
|
||||||
|
@data ||= JSON.parse(execute('docker', 'inspect', cid)).first
|
||||||
|
end
|
||||||
|
|
||||||
|
def all_containers
|
||||||
|
execute('docker', 'ps', '-a', '-q', '-notrunc').to_s.split
|
||||||
|
end
|
||||||
|
|
||||||
|
def docker_bridge_ip
|
||||||
|
output = execute('/sbin/ip', '-4', 'addr', 'show', 'scope', 'global', 'docker0')
|
||||||
|
if output =~ /^\s+inet ([0-9.]+)\/[0-9]+\s+/
|
||||||
|
return $1.to_s
|
||||||
|
else
|
||||||
|
# TODO: Raise an user friendly message
|
||||||
|
raise 'Unable to fetch docker bridge IP!'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def execute(*cmd, &block)
|
||||||
|
result = raw(*cmd, &block)
|
||||||
|
|
||||||
|
if result.exit_code != 0
|
||||||
|
if @interrupted
|
||||||
|
@logger.info("Exit code != 0, but interrupted. Ignoring.")
|
||||||
|
else
|
||||||
|
msg = result.stdout.gsub("\r\n", "\n")
|
||||||
|
msg << result.stderr.gsub("\r\n", "\n")
|
||||||
|
raise "#{cmd.inspect}\n#{msg}" #Errors::ExecuteError, :command => command.inspect
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return the output, making sure to replace any Windows-style
|
||||||
|
# newlines with Unix-style.
|
||||||
|
result.stdout.gsub("\r\n", "\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
def raw(*cmd, &block)
|
||||||
|
int_callback = lambda do
|
||||||
|
@interrupted = true
|
||||||
|
@logger.info("Interrupted.")
|
||||||
|
end
|
||||||
|
|
||||||
|
# Append in the options for subprocess
|
||||||
|
cmd << { :notify => [:stdout, :stderr] }
|
||||||
|
|
||||||
|
Vagrant::Util::Busy.busy(int_callback) do
|
||||||
|
Vagrant::Util::Subprocess.execute(*cmd, &block)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,14 @@
|
||||||
|
require 'vagrant/errors'
|
||||||
|
|
||||||
|
module VagrantPlugins
|
||||||
|
module DockerProvider
|
||||||
|
module Errors
|
||||||
|
class ImageNotConfiguredError < Vagrant::Errors::VagrantError
|
||||||
|
error_key(:docker_provider_image_not_configured)
|
||||||
|
end
|
||||||
|
class NfsWithoutPrivilegedError < Vagrant::Errors::VagrantError
|
||||||
|
error_key(:docker_provider_nfs_without_privileged)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,31 @@
|
||||||
|
# TODO: Switch to Vagrant.require_version before 1.0.0
|
||||||
|
# see: https://github.com/mitchellh/vagrant/blob/bc55081e9ffaa6820113e449a9f76b293a29b27d/lib/vagrant.rb#L202-L228
|
||||||
|
unless Gem::Requirement.new('>= 1.4.0').satisfied_by?(Gem::Version.new(Vagrant::VERSION))
|
||||||
|
raise 'docker-provider requires Vagrant >= 1.4.0 in order to work!'
|
||||||
|
end
|
||||||
|
|
||||||
|
I18n.load_path << File.expand_path(File.dirname(__FILE__) + '/../../locales/en.yml')
|
||||||
|
I18n.reload!
|
||||||
|
|
||||||
|
module VagrantPlugins
|
||||||
|
module DockerProvider
|
||||||
|
class Plugin < Vagrant.plugin("2")
|
||||||
|
name "docker-provider"
|
||||||
|
|
||||||
|
provider(:docker, parallel: true) do
|
||||||
|
require_relative 'provider'
|
||||||
|
Provider
|
||||||
|
end
|
||||||
|
|
||||||
|
config(:docker, :provider) do
|
||||||
|
require_relative 'config'
|
||||||
|
Config
|
||||||
|
end
|
||||||
|
|
||||||
|
synced_folder(:docker) do
|
||||||
|
require File.expand_path("../synced_folder", __FILE__)
|
||||||
|
SyncedFolder
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,61 @@
|
||||||
|
require "log4r"
|
||||||
|
|
||||||
|
require_relative 'driver'
|
||||||
|
require_relative 'action'
|
||||||
|
|
||||||
|
module VagrantPlugins
|
||||||
|
module DockerProvider
|
||||||
|
class Provider < Vagrant.plugin("2", :provider)
|
||||||
|
attr_reader :driver
|
||||||
|
|
||||||
|
def initialize(machine)
|
||||||
|
@logger = Log4r::Logger.new("vagrant::provider::docker")
|
||||||
|
@machine = machine
|
||||||
|
@driver = Driver.new
|
||||||
|
end
|
||||||
|
|
||||||
|
# @see Vagrant::Plugin::V2::Provider#action
|
||||||
|
def action(name)
|
||||||
|
action_method = "action_#{name}"
|
||||||
|
return Action.send(action_method) if Action.respond_to?(action_method)
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns the SSH info for accessing the Container.
|
||||||
|
def ssh_info
|
||||||
|
# If the Container is not created then we cannot possibly SSH into it, so
|
||||||
|
# we return nil.
|
||||||
|
return nil if state == :not_created
|
||||||
|
|
||||||
|
network = @driver.inspect_container(@machine.id)['NetworkSettings']
|
||||||
|
ip = network['IPAddress']
|
||||||
|
|
||||||
|
# If we were not able to identify the container's IP, we return nil
|
||||||
|
# here and we let Vagrant core deal with it ;)
|
||||||
|
return nil unless ip
|
||||||
|
|
||||||
|
{
|
||||||
|
:host => ip,
|
||||||
|
:port => @machine.config.ssh.guest_port
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def state
|
||||||
|
state_id = nil
|
||||||
|
state_id = :not_created if !@machine.id || !@driver.created?(@machine.id)
|
||||||
|
state_id = @driver.state(@machine.id) if @machine.id && !state_id
|
||||||
|
state_id = :unknown if !state_id
|
||||||
|
|
||||||
|
short = state_id.to_s.gsub("_", " ")
|
||||||
|
long = I18n.t("vagrant.commands.status.#{state_id}")
|
||||||
|
|
||||||
|
Vagrant::MachineState.new(state_id, short, long)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s
|
||||||
|
id = @machine.id ? @machine.id : "new container"
|
||||||
|
"Docker (#{id})"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,20 @@
|
||||||
|
module VagrantPlugins
|
||||||
|
module DockerProvider
|
||||||
|
class SyncedFolder < Vagrant.plugin("2", :synced_folder)
|
||||||
|
def usable?(machine)
|
||||||
|
# These synced folders only work if the provider is Docker
|
||||||
|
machine.provider_name == :docker
|
||||||
|
end
|
||||||
|
|
||||||
|
def prepare(machine, folders, _opts)
|
||||||
|
# FIXME: Check whether the container has already been created with
|
||||||
|
# different synced folders and let the user know about it
|
||||||
|
folders.each do |id, data|
|
||||||
|
host_path = File.expand_path(data[:hostpath], machine.env.root_path)
|
||||||
|
guest_path = data[:guestpath]
|
||||||
|
machine.provider_config.volumes << "#{host_path}:#{guest_path}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,32 @@
|
||||||
|
en:
|
||||||
|
docker_provider:
|
||||||
|
messages:
|
||||||
|
not_created: |-
|
||||||
|
The container hasn't been created yet.
|
||||||
|
not_running: |-
|
||||||
|
The container is not currently running.
|
||||||
|
will_not_destroy: |-
|
||||||
|
The container '%{name}' will not be destroyed, since the confirmation
|
||||||
|
was declined.
|
||||||
|
starting: |-
|
||||||
|
Starting container...
|
||||||
|
stopping: |-
|
||||||
|
Stopping container...
|
||||||
|
container_ready: |-
|
||||||
|
Container started and ready for use!
|
||||||
|
|
||||||
|
errors:
|
||||||
|
config:
|
||||||
|
cmd_not_set: |-
|
||||||
|
The Docker command has not been set!
|
||||||
|
|
||||||
|
vagrant:
|
||||||
|
errors:
|
||||||
|
docker_provider_nfs_without_privileged: |-
|
||||||
|
You've configured a NFS synced folder but didn't enable privileged
|
||||||
|
mode for the container. Please set the `privileged` option to true
|
||||||
|
on the provider block from your Vagrantfile, recreate the container
|
||||||
|
and try again.
|
||||||
|
|
||||||
|
docker_provider_image_not_configured: |-
|
||||||
|
The base Docker image has not been set for the '%{name}' VM!
|
|
@ -0,0 +1,202 @@
|
||||||
|
require_relative "../../../base"
|
||||||
|
|
||||||
|
require Vagrant.source_root.join("plugins/providers/docker/driver")
|
||||||
|
|
||||||
|
describe VagrantPlugins::DockerProvider::Driver do
|
||||||
|
let(:cmd_executed) { @cmd }
|
||||||
|
let(:cid) { 'side-1-song-10' }
|
||||||
|
|
||||||
|
before do
|
||||||
|
subject.stub(:execute) { |*args| @cmd = args.join(' ') }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#create' do
|
||||||
|
let(:params) { {
|
||||||
|
image: 'jimi/hendrix:eletric-ladyland',
|
||||||
|
cmd: ['play', 'voodoo-chile'],
|
||||||
|
ports: '8080:80',
|
||||||
|
volumes: '/host/path:guest/path',
|
||||||
|
name: cid,
|
||||||
|
hostname: 'jimi-hendrix',
|
||||||
|
privileged: true
|
||||||
|
} }
|
||||||
|
|
||||||
|
before { subject.create(params) }
|
||||||
|
|
||||||
|
it 'runs a detached docker image' do
|
||||||
|
expect(cmd_executed).to match(/^docker run .+ -d .+ #{Regexp.escape params[:image]}/)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sets container name' do
|
||||||
|
expect(cmd_executed).to match(/-name #{Regexp.escape params[:name]}/)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'forwards ports' do
|
||||||
|
expect(cmd_executed).to match(/-p #{params[:ports]} .+ #{Regexp.escape params[:image]}/)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'shares folders' do
|
||||||
|
expect(cmd_executed).to match(/-v #{params[:volumes]} .+ #{Regexp.escape params[:image]}/)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'is able to run a privileged container' do
|
||||||
|
expect(cmd_executed).to match(/-privileged .+ #{Regexp.escape params[:image]}/)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sets the hostname if specified' do
|
||||||
|
expect(cmd_executed).to match(/-h #{params[:hostname]} #{Regexp.escape params[:image]}/)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'executes the provided command' do
|
||||||
|
expect(cmd_executed).to match(/#{Regexp.escape params[:image]} #{Regexp.escape params[:cmd].join(' ')}/)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#created?' do
|
||||||
|
let(:result) { subject.created?(cid) }
|
||||||
|
|
||||||
|
it 'performs the check on all containers list' do
|
||||||
|
subject.created?(cid)
|
||||||
|
expect(cmd_executed).to match(/docker ps \-a \-q/)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when container exists' do
|
||||||
|
before { subject.stub(execute: "foo\n#{cid}\nbar") }
|
||||||
|
it { expect(result).to be_true }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when container does not exist' do
|
||||||
|
before { subject.stub(execute: "foo\n#{cid}extra\nbar") }
|
||||||
|
it { expect(result).to be_false }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#running?' do
|
||||||
|
let(:result) { subject.running?(cid) }
|
||||||
|
|
||||||
|
it 'performs the check on the running containers list' do
|
||||||
|
subject.running?(cid)
|
||||||
|
expect(cmd_executed).to match(/docker ps \-q/)
|
||||||
|
expect(cmd_executed).to_not include('-a')
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when container exists' do
|
||||||
|
before { subject.stub(execute: "foo\n#{cid}\nbar") }
|
||||||
|
it { expect(result).to be_true }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when container does not exist' do
|
||||||
|
before { subject.stub(execute: "foo\n#{cid}extra\nbar") }
|
||||||
|
it { expect(result).to be_false }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#privileged?' do
|
||||||
|
it 'identifies privileged containers' do
|
||||||
|
subject.stub(inspect_container: {'HostConfig' => {"Privileged" => true}})
|
||||||
|
expect(subject).to be_privileged(cid)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'identifies unprivileged containers' do
|
||||||
|
subject.stub(inspect_container: {'HostConfig' => {"Privileged" => false}})
|
||||||
|
expect(subject).to_not be_privileged(cid)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#start' do
|
||||||
|
context 'when container is running' do
|
||||||
|
before { subject.stub(running?: true) }
|
||||||
|
|
||||||
|
it 'does not start the container' do
|
||||||
|
subject.should_not_receive(:execute).with('docker', 'start', cid)
|
||||||
|
subject.start(cid)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when container is not running' do
|
||||||
|
before { subject.stub(running?: false) }
|
||||||
|
|
||||||
|
it 'starts the container' do
|
||||||
|
subject.should_receive(:execute).with('docker', 'start', cid)
|
||||||
|
subject.start(cid)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#stop' do
|
||||||
|
context 'when container is running' do
|
||||||
|
before { subject.stub(running?: true) }
|
||||||
|
|
||||||
|
it 'stops the container' do
|
||||||
|
subject.should_receive(:execute).with('docker', 'stop', '-t', '1', cid)
|
||||||
|
subject.stop(cid)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when container is not running' do
|
||||||
|
before { subject.stub(running?: false) }
|
||||||
|
|
||||||
|
it 'does not stop container' do
|
||||||
|
subject.should_not_receive(:execute).with('docker', 'stop', '-t', '1', cid)
|
||||||
|
subject.stop(cid)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#rm' do
|
||||||
|
context 'when container has been created' do
|
||||||
|
before { subject.stub(created?: true) }
|
||||||
|
|
||||||
|
it 'removes the container' do
|
||||||
|
subject.should_receive(:execute).with('docker', 'rm', '-v', cid)
|
||||||
|
subject.rm(cid)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when container has not been created' do
|
||||||
|
before { subject.stub(created?: false) }
|
||||||
|
|
||||||
|
it 'does not attempt to remove the container' do
|
||||||
|
subject.should_not_receive(:execute).with('docker', 'rm', '-v', cid)
|
||||||
|
subject.rm(cid)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#inspect_container' do
|
||||||
|
let(:data) { '[{"json": "value"}]' }
|
||||||
|
|
||||||
|
before { subject.stub(execute: data) }
|
||||||
|
|
||||||
|
it 'inspects the container' do
|
||||||
|
subject.should_receive(:execute).with('docker', 'inspect', cid)
|
||||||
|
subject.inspect_container(cid)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'parses the json output' do
|
||||||
|
expect(subject.inspect_container(cid)).to eq('json' => 'value')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#all_containers' do
|
||||||
|
let(:containers) { "container1\ncontainer2" }
|
||||||
|
|
||||||
|
before { subject.stub(execute: containers) }
|
||||||
|
|
||||||
|
it 'returns an array of all known containers' do
|
||||||
|
subject.should_receive(:execute).with('docker', 'ps', '-a', '-q', '-notrunc')
|
||||||
|
expect(subject.all_containers).to eq(['container1', 'container2'])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#docker_bridge_ip' do
|
||||||
|
let(:containers) { " inet 123.456.789.012/16 " }
|
||||||
|
|
||||||
|
before { subject.stub(execute: containers) }
|
||||||
|
|
||||||
|
it 'returns an array of all known containers' do
|
||||||
|
subject.should_receive(:execute).with('/sbin/ip', '-4', 'addr', 'show', 'scope', 'global', 'docker0')
|
||||||
|
expect(subject.docker_bridge_ip).to eq('123.456.789.012')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue