Prior to this commit, the docker compose driver would _always_ path expand a host volume no matter what. This is not always the correct option, for example if that host volume is actually a reference to a key inside a `volumes` hash instead of a path on disk. This commit changes that by looking to see if the requested host volume is actually a defined key inside the compose config, and if not, it will path expand it like before. Otherwise it will leave the key "as is".
315 lines
10 KiB
Ruby
315 lines
10 KiB
Ruby
require_relative "../../../base"
|
|
|
|
require Vagrant.source_root.join("lib/vagrant/util/deep_merge")
|
|
require Vagrant.source_root.join("plugins/providers/docker/driver")
|
|
|
|
describe VagrantPlugins::DockerProvider::Driver::Compose do
|
|
let(:cmd_executed) { @cmd }
|
|
let(:cid) { 'side-1-song-10' }
|
|
let(:docker_yml){ double("docker-yml", path: "/tmp-file") }
|
|
let(:machine){ double("machine", env: env, name: :docker_1, id: :docker_id, provider_config: provider_config) }
|
|
let(:compose_configuration){ {} }
|
|
let(:provider_config) do
|
|
double("provider-config",
|
|
compose: true,
|
|
compose_configuration: compose_configuration
|
|
)
|
|
end
|
|
let(:env) do
|
|
double("env",
|
|
cwd: Pathname.new("/compose/cwd"),
|
|
local_data_path: local_data_path
|
|
)
|
|
end
|
|
let(:composition_content){ "--- {}\n" }
|
|
let(:composition_path) do
|
|
double("composition-path",
|
|
to_s: "docker-compose.yml",
|
|
exist?: true,
|
|
read: composition_content,
|
|
delete: true
|
|
)
|
|
end
|
|
let(:data_directory){ double("data-directory", join: composition_path) }
|
|
let(:local_data_path){ double("local-data-path") }
|
|
let(:compose_execute_up){ ["docker-compose", "-f", "docker-compose.yml", "-p", "cwd", "up", "--remove-orphans", "-d", {}] }
|
|
|
|
|
|
subject{ described_class.new(machine) }
|
|
|
|
before do
|
|
allow(Vagrant::Util::Which).to receive(:which).and_return("/dev/null/docker-compose")
|
|
allow(env).to receive(:lock).and_yield
|
|
allow(Pathname).to receive(:new).with(local_data_path).and_return(local_data_path)
|
|
allow(Pathname).to receive(:new).with('/host/path').and_call_original
|
|
allow(local_data_path).to receive(:join).and_return(data_directory)
|
|
allow(data_directory).to receive(:mkpath)
|
|
allow(FileUtils).to receive(:mv)
|
|
allow(Tempfile).to receive(:new).with("vagrant-docker-compose").and_return(docker_yml)
|
|
allow(docker_yml).to receive(:write)
|
|
allow(docker_yml).to receive(:close)
|
|
allow(subject).to receive(:execute) do |*args|
|
|
args.delete_if{|i| i.is_a?(Hash) }
|
|
@cmd = args.join(' ')
|
|
end
|
|
end
|
|
|
|
describe '#create' do
|
|
let(:params) { {
|
|
image: 'jimi/hendrix:electric-ladyland',
|
|
cmd: ['play', 'voodoo-chile'],
|
|
ports: '8080:80',
|
|
volumes: '/host/path:guest/path',
|
|
detach: true,
|
|
links: [[:janis, 'joplin'], [:janis, 'janis']],
|
|
env: {key: 'value'},
|
|
name: cid,
|
|
hostname: 'jimi-hendrix',
|
|
privileged: true
|
|
} }
|
|
|
|
before { expect(subject).to receive(:execute).with(*compose_execute_up) }
|
|
after { subject.create(params) }
|
|
|
|
it 'sets container name' do
|
|
expect(docker_yml).to receive(:write).with(/#{machine.name}/)
|
|
end
|
|
|
|
it 'forwards ports' do
|
|
expect(docker_yml).to receive(:write).with(/#{params[:ports]}/)
|
|
end
|
|
|
|
it 'shares folders' do
|
|
expect(docker_yml).to receive(:write).with(/#{params[:volumes]}/)
|
|
end
|
|
|
|
context 'when links are provided as strings' do
|
|
before{ params[:links] = ["linkl1:linkr1", "linkl2:linkr2"] }
|
|
|
|
it 'links containers' do
|
|
params[:links].flatten.map{|l| l.split(':')}.each do |link|
|
|
expect(docker_yml).to receive(:write).with(/#{link}/)
|
|
end
|
|
subject.create(params)
|
|
end
|
|
end
|
|
|
|
context 'with relative path in share folders' do
|
|
before do
|
|
params[:volumes] = './path:guest/path'
|
|
allow(Pathname).to receive(:new).with('./path').and_call_original
|
|
allow(Pathname).to receive(:new).with('/compose/cwd/path').and_call_original
|
|
end
|
|
|
|
it 'should expand the relative host directory' do
|
|
expect(docker_yml).to receive(:write).with(%r{/compose/cwd/path})
|
|
end
|
|
end
|
|
|
|
context 'with a volumes key in use for mounting' do
|
|
let(:compose_config) { {"volumes"=>{"my_volume_key"=>"data"}} }
|
|
|
|
before do
|
|
params[:volumes] = 'my_volume_key:my/guest/path'
|
|
allow(Pathname).to receive(:new).with('./path').and_call_original
|
|
allow(Pathname).to receive(:new).with('my_volume_key').and_call_original
|
|
allow(Pathname).to receive(:new).with('/compose/cwd/my_volume_key').and_call_original
|
|
allow(subject).to receive(:get_composition).and_return(compose_config)
|
|
end
|
|
|
|
it 'should not expand the relative host directory' do
|
|
expect(docker_yml).to receive(:write).with(%r{my_volume_key})
|
|
end
|
|
end
|
|
|
|
it 'links containers' do
|
|
params[:links].each do |link|
|
|
expect(docker_yml).to receive(:write).with(/#{link}/)
|
|
end
|
|
subject.create(params)
|
|
end
|
|
|
|
it 'sets environmental variables' do
|
|
expect(docker_yml).to receive(:write).with(/key.*value/)
|
|
end
|
|
|
|
it 'is able to run a privileged container' do
|
|
expect(docker_yml).to receive(:write).with(/privileged/)
|
|
end
|
|
|
|
it 'sets the hostname if specified' do
|
|
expect(docker_yml).to receive(:write).with(/#{params[:hostname]}/)
|
|
end
|
|
|
|
it 'executes the provided command' do
|
|
expect(docker_yml).to receive(:write).with(/#{params[:image]}/)
|
|
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 { allow(subject).to receive(:execute)
|
|
.and_return("foo\n#{cid}\nbar") }
|
|
it { expect(result).to be_truthy }
|
|
end
|
|
|
|
context 'when container does not exist' do
|
|
before { allow(subject).to receive(:execute)
|
|
.and_return("foo\n#{cid}extra\nbar") }
|
|
it { expect(result).to be_falsey }
|
|
end
|
|
end
|
|
|
|
describe '#pull' do
|
|
it 'should pull images' do
|
|
expect(subject).to receive(:execute).with('docker', 'pull', 'foo')
|
|
subject.pull('foo')
|
|
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 { allow(subject).to receive(:execute)
|
|
.and_return("foo\n#{cid}\nbar") }
|
|
it { expect(result).to be_truthy }
|
|
end
|
|
|
|
context 'when container does not exist' do
|
|
before { allow(subject).to receive(:execute)
|
|
.and_return("foo\n#{cid}extra\nbar") }
|
|
it { expect(result).to be_falsey }
|
|
end
|
|
end
|
|
|
|
describe '#privileged?' do
|
|
it 'identifies privileged containers' do
|
|
allow(subject).to receive(:inspect_container)
|
|
.and_return({'HostConfig' => {"Privileged" => true}})
|
|
expect(subject).to be_privileged(cid)
|
|
end
|
|
|
|
it 'identifies unprivileged containers' do
|
|
allow(subject).to receive(:inspect_container)
|
|
.and_return({'HostConfig' => {"Privileged" => false}})
|
|
expect(subject).to_not be_privileged(cid)
|
|
end
|
|
end
|
|
|
|
describe '#start' do
|
|
context 'when container is running' do
|
|
before { allow(subject).to receive(:running?).and_return(true) }
|
|
|
|
it 'does not start the container' do
|
|
expect(subject).not_to receive(:execute).with('docker', 'start', cid)
|
|
subject.start(cid)
|
|
end
|
|
end
|
|
|
|
context 'when container is not running' do
|
|
before { allow(subject).to receive(:running?).and_return(false) }
|
|
|
|
it 'starts the container' do
|
|
expect(subject).to receive(:execute).with('docker', 'start', cid)
|
|
subject.start(cid)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#stop' do
|
|
context 'when container is running' do
|
|
before { allow(subject).to receive(:running?).and_return(true) }
|
|
|
|
it 'stops the container' do
|
|
expect(subject).to receive(:execute).with('docker', 'stop', '-t', '1', cid)
|
|
subject.stop(cid, 1)
|
|
end
|
|
|
|
it "stops the container with the set timeout" do
|
|
expect(subject).to receive(:execute).with('docker', 'stop', '-t', '5', cid)
|
|
subject.stop(cid, 5)
|
|
end
|
|
end
|
|
|
|
context 'when container is not running' do
|
|
before { allow(subject).to receive(:running?).and_return(false) }
|
|
|
|
it 'does not stop container' do
|
|
expect(subject).not_to receive(:execute).with('docker', 'stop', '-t', '1', cid)
|
|
subject.stop(cid, 1)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#rm' do
|
|
context 'when container has been created' do
|
|
before { allow(subject).to receive(:created?).and_return(true) }
|
|
|
|
it 'removes the container' do
|
|
expect(subject).to receive(:execute).with("docker-compose", "-f", "docker-compose.yml", "-p", "cwd", "rm", "-f", "docker_1", {})
|
|
subject.rm(cid)
|
|
end
|
|
end
|
|
|
|
context 'when container has not been created' do
|
|
before { allow(subject).to receive(:created?).and_return(false) }
|
|
|
|
it 'does not attempt to remove the container' do
|
|
expect(subject).not_to receive(:execute).with("docker-compose", "-f", "docker-compose.yml", "-p", "cwd", "rm", "-f", "docker_1", {})
|
|
subject.rm(cid)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#inspect_container' do
|
|
let(:data) { '[{"json": "value"}]' }
|
|
|
|
before { allow(subject).to receive(:execute).and_return(data) }
|
|
|
|
it 'inspects the container' do
|
|
expect(subject).to 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 { allow(subject).to receive(:execute).and_return(containers) }
|
|
|
|
it 'returns an array of all known containers' do
|
|
expect(subject).to receive(:execute).with('docker', 'ps', '-a', '-q', '--no-trunc')
|
|
expect(subject.all_containers).to eq(['container1', 'container2'])
|
|
end
|
|
end
|
|
|
|
describe '#docker_bridge_ip' do
|
|
let(:containers) { " inet 123.456.789.012/16 " }
|
|
|
|
before { allow(subject).to receive(:execute).and_return(containers) }
|
|
|
|
it 'returns an array of all known containers' do
|
|
expect(subject).to 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
|