Don't rely on `/sbin/ip` to fetch the docker bridge ip address, instead first attempt to use the docker command to fetch it. If it fails, fall back to previous behavior.
349 lines
11 KiB
Ruby
349 lines
11 KiB
Ruby
require "yaml"
|
|
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(:execute_result) {
|
|
double("execute_result",
|
|
exit_code: exit_code,
|
|
stderr: stderr,
|
|
stdout: stdout
|
|
)
|
|
}
|
|
let(:exit_code) { 0 }
|
|
let(:stderr) { "" }
|
|
let(:stdout) { "" }
|
|
|
|
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", any_args] }
|
|
let(:compose_execute_up_regex) { /docker-compose -f docker-compose.yml -p cwd up --remove-orphans -d/ }
|
|
|
|
subject{ described_class.new(machine) }
|
|
|
|
before do
|
|
@cmd = []
|
|
allow(Vagrant::Util::Subprocess).to receive(:execute) { |*args|
|
|
if args.last.is_a?(Hash)
|
|
args = args[0, args.size - 1]
|
|
end
|
|
invalid = args.detect { |a| !a.is_a?(String) }
|
|
if invalid
|
|
raise TypeError,
|
|
"Vagrant::Util::Subprocess#execute only accepts signle option Hash and String arguments, received `#{invalid.class}'"
|
|
end
|
|
@cmd << args.join(" ")
|
|
}.and_return(execute_result)
|
|
allow_any_instance_of(Vagrant::Errors::VagrantError).
|
|
to receive(:translate_error) { |*args| args.join(" ") }
|
|
|
|
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)
|
|
end
|
|
|
|
describe '#build' do
|
|
it 'creates a compose config with no extra options' do
|
|
expect(subject).to receive(:update_composition)
|
|
subject.build(composition_path)
|
|
end
|
|
|
|
it 'creates a compose config when given an array for build-arg' do
|
|
expect(subject).to receive(:update_composition)
|
|
subject.build(composition_path, extra_args: ["foo", "bar"])
|
|
end
|
|
|
|
it 'creates a compose config when given a hash for build-arg' do
|
|
expect(subject).to receive(:update_composition)
|
|
subject.build(composition_path, extra_args: {"foo"=>"bar"})
|
|
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
|
|
} }
|
|
|
|
after {
|
|
subject.create(params)
|
|
expect(cmd_executed.first).to match(compose_execute_up_regex)
|
|
}
|
|
|
|
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.first).to match(/docker ps \-a \-q/)
|
|
end
|
|
|
|
context 'when container exists' do
|
|
let(:stdout) { "foo\n#{cid}\nbar" }
|
|
it { expect(result).to be_truthy }
|
|
end
|
|
|
|
context 'when container does not exist' do
|
|
let(:stdout) { "foo\n#{cid}extra\nbar" }
|
|
it { expect(result).to be_falsey }
|
|
end
|
|
end
|
|
|
|
describe '#pull' do
|
|
it 'should pull images' do
|
|
subject.pull('foo')
|
|
expect(cmd_executed.first).to eq("docker 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.first).to match(/docker ps \-q/)
|
|
expect(cmd_executed.first).to_not include('-a')
|
|
end
|
|
|
|
context 'when container exists' do
|
|
let(:stdout) { "foo\n#{cid}\nbar" }
|
|
it { expect(result).to be_truthy }
|
|
end
|
|
|
|
context 'when container does not exist' do
|
|
let(:stdout) { "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
|
|
subject.start(cid)
|
|
expect(cmd_executed).to be_empty
|
|
end
|
|
end
|
|
|
|
context 'when container is not running' do
|
|
before { allow(subject).to receive(:running?).and_return(false) }
|
|
|
|
it 'starts the container' do
|
|
subject.start(cid)
|
|
expect(cmd_executed.first).to eq("docker 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
|
|
subject.stop(cid, 1)
|
|
expect(cmd_executed.first).to eq("docker stop -t 1 #{cid}")
|
|
end
|
|
|
|
it "stops the container with the set timeout" do
|
|
subject.stop(cid, 5)
|
|
expect(cmd_executed.first).to eq("docker stop -t 5 #{cid}")
|
|
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)
|
|
expect(cmd_executed).to be_empty
|
|
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
|
|
subject.rm(cid)
|
|
expect(cmd_executed.first).to match(/docker-compose -f docker-compose.yml -p cwd rm -f docker_1/)
|
|
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
|
|
subject.rm(cid)
|
|
expect(cmd_executed).to be_empty
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#inspect_container' do
|
|
let(:stdout) { '[{"json": "value"}]' }
|
|
|
|
it 'inspects the container' do
|
|
subject.inspect_container(cid)
|
|
expect(cmd_executed.first).to eq("docker inspect #{cid}")
|
|
end
|
|
|
|
it 'parses the json output' do
|
|
expect(subject.inspect_container(cid)).to eq('json' => 'value')
|
|
end
|
|
end
|
|
|
|
describe '#all_containers' do
|
|
let(:stdout) { "container1\ncontainer2" }
|
|
|
|
it 'returns an array of all known containers' do
|
|
expect(subject.all_containers).to eq(['container1', 'container2'])
|
|
expect(cmd_executed.first).to eq("docker ps -a -q --no-trunc")
|
|
end
|
|
end
|
|
|
|
describe '#docker_bridge_ip' do
|
|
let(:stdout) { " inet 123.456.789.012/16 " }
|
|
|
|
it 'returns the bridge ip' do
|
|
expect(subject.docker_bridge_ip).to eq('123.456.789.012')
|
|
expect(cmd_executed.first).to eq("docker network inspect bridge")
|
|
expect(cmd_executed.last).to eq("ip -4 addr show scope global docker0")
|
|
end
|
|
end
|
|
end
|