Jaroslav Prokop 5208032594 Docker provider: catch container name when using podman.
When user is using podman's docker CLI emulation the containers would
fail to enter running state because the docker driver could not catch
the container name. This commit fixes that by adding a check if podman
docker emulation is used and pick the container hash correctly from the
output.
2020-03-25 19:34:31 +01:00

628 lines
20 KiB
Ruby

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
allow(subject).to receive(:execute) { |*args| @cmd = args.join(' ') }
end
let(:docker_network_struct) {
[
{
"Name": "bridge",
"Id": "ae74f6cc18bbcde86326937797070b814cc71bfc4a6d8e3e8cf3b2cc5c7f4a7d",
"Created": "2019-03-20T14:10:06.313314662-07:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": nil,
"Config": [
{
"Subnet": "172.17.0.0/16",
"Gateway": "172.17.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"a1ee9b12bcea8268495b1f43e8d1285df1925b7174a695075f6140adb9415d87": {
"Name": "vagrant-sandbox_docker-1_1553116237",
"EndpointID": "fc1b0ed6e4f700cf88bb26a98a0722655191542e90df3e3492461f4d1f3c0cae",
"MacAddress": "02:42:ac:11:00:02",
"IPv4Address": "172.17.0.2/16",
"IPv6Address": ""
}
},
"Options": {
"com.docker.network.bridge.default_bridge": "true",
"com.docker.network.bridge.enable_icc": "true",
"com.docker.network.bridge.enable_ip_masquerade": "true",
"com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
"com.docker.network.bridge.name": "docker0",
"com.docker.network.driver.mtu": "1500"
},
"Labels": {}
},
{
"Name": "host",
"Id": "2a2845e77550e33bf3e97bda8b71477ac7d3ccf78bc9102585fdb6056fb84cbf",
"Created": "2018-09-28T10:54:08.633543196-07:00",
"Scope": "local",
"Driver": "host",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": nil,
"Config": []
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {},
"Options": {},
"Labels": {}
},
{
"Name": "vagrant_network",
"Id": "93385d4fd3cf7083a36e62fa72a0ad0a21203d0ddf48409c32b550cd8462b3ba",
"Created": "2019-03-20T14:10:36.828235585-07:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "172.18.0.0/16",
"Gateway": "172.18.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"a1ee9b12bcea8268495b1f43e8d1285df1925b7174a695075f6140adb9415d87": {
"Name": "vagrant-sandbox_docker-1_1553116237",
"EndpointID": "9502cd9d37ae6815e3ffeb0bc2de9b84f79e7223e8a1f8f4ccc79459e96c7914",
"MacAddress": "02:42:ac:12:00:02",
"IPv4Address": "172.18.0.2/16",
"IPv6Address": ""
}
},
"Options": {},
"Labels": {}
},
{
"Name": "vagrant_network_172.20.0.0/16",
"Id": "649f0ab3ef0eef6f2a025c0d0398bd7b9b4d05ec88b0d7bd573b44153d903cfb",
"Created": "2019-03-20T14:10:37.088885647-07:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "172.20.0.0/16"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"a1ee9b12bcea8268495b1f43e8d1285df1925b7174a695075f6140adb9415d87": {
"Name": "vagrant-sandbox_docker-1_1553116237",
"EndpointID": "e19156f8018f283468227fa97c145f4ea0eaba652fb7e977a0c759b1c3ec168a",
"MacAddress": "02:42:ac:14:80:02",
"IPv4Address": "172.20.0.2/16",
"IPv6Address": ""
}
},
"Options": {},
"Labels": {}
}
].to_json }
describe '#build' do
let(:result) { "Successfully built other_package\nSuccessfully built 1a2b3c4d" }
let(:buildkit_result) { "writing image sha256:1a2b3c4d done" }
let(:podman_result) { "1a2b3c4d5e6f7g8h9i10j11k12l13m14n16o17p18q19r20s21t22u23v24w25x2" }
let(:cid) { "1a2b3c4d" }
it "builds a container with standard docker" do
allow(subject).to receive(:execute).and_return(result)
container_id = subject.build("/tmp/fakedir")
expect(container_id).to eq(cid)
end
it "builds a container with buildkit docker" do
allow(subject).to receive(:execute).and_return(buildkit_result)
container_id = subject.build("/tmp/fakedir")
expect(container_id).to eq(cid)
end
it "builds a container with podman emulating docker CLI" do
allow(subject).to receive(:execute).and_return(podman_result)
allow(subject).to receive(:podman?).and_return(true)
container_id = subject.build("/tmp/fakedir")
expect(container_id).to eq(cid)
end
end
describe '#podman?' do
let(:emulating_docker_output) { "podman version 1.7.1-dev" }
let(:real_docker_output) { "Docker version 1.8.1, build d12ea79" }
it 'returns false when docker is used' do
allow(subject).to receive(:execute).and_return(real_docker_output)
expect(subject.podman?).to be false
end
it 'returns true when podman is used' do
allow(subject).to receive(:execute).and_return(emulating_docker_output)
expect(subject.podman?).to be true
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 { 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 'links containers' do
params[:links].each do |link|
expect(cmd_executed).to match(/--link #{link.join(':')} .+ #{Regexp.escape params[:image]}/)
end
end
it 'sets environmental variables' do
expect(cmd_executed).to match(/-e key=value .+ #{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 '#create windows' do
let(:params) { {
image: 'jimi/hendrix:eletric-ladyland',
cmd: ['play', 'voodoo-chile'],
ports: '8080:80',
volumes: 'C:/Users/BobDylan/AllAlong:/The/Watchtower',
detach: true,
links: [[:janis, 'joplin'], [:janis, 'janis']],
env: {key: 'value'},
name: cid,
hostname: 'jimi-hendrix',
privileged: true
} }
let(:translated_path) { "//c/Users/BobDylan/AllAlong:/The/Watchtower" }
before do
allow(Vagrant::Util::Platform).to receive(:windows?).and_return(true)
subject.create(params)
end
it 'shares folders' do
expect(cmd_executed).to match(/-v #{translated_path} .+ #{Regexp.escape 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).to_not 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).to_not 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', 'rm', '-f', '-v', cid)
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).to_not receive(:execute).with('docker', 'rm', '-f', '-v', cid)
subject.rm(cid)
end
end
end
describe '#rmi' do
let(:id) { 'asdg21ew' }
context 'image exists' do
it "removes the image" do
expect(subject).to receive(:execute).with('docker', 'rmi', id)
subject.rmi(id)
end
end
context 'image is being used by running container' do
before { allow(subject).to receive(:execute).and_raise("image is being used by running container") }
it 'does not remove the image' do
expect(subject.rmi(id)).to eq(false)
subject.rmi(id)
end
end
context 'image is being used by stopped container' do
before { allow(subject).to receive(:execute).and_raise("image is being used by stopped container") }
it 'does not remove the image' do
expect(subject.rmi(id)).to eq(false)
subject.rmi(id)
end
end
context 'container is using it' do
before { allow(subject).to receive(:execute).and_raise("container is using it") }
it 'does not remove the image' do
expect(subject.rmi(id)).to eq(false)
subject.rmi(id)
end
end
context 'image does not exist' do
before { allow(subject).to receive(:execute).and_raise("No such image") }
it 'raises an error' do
expect(subject.rmi(id)).to eq(nil)
subject.rmi(id)
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
describe '#docker_connect_network' do
let(:opts) { ["--ip", "172.20.128.2"] }
it 'connects a network to a container' do
expect(subject).to receive(:execute).with("docker", "network", "connect", "vagrant_network", cid, "--ip", "172.20.128.2")
subject.connect_network("vagrant_network", cid, opts)
end
end
describe '#docker_create_network' do
let(:opts) { ["--subnet", "172.20.0.0/16"] }
it 'creates a network' do
expect(subject).to receive(:execute).with("docker", "network", "create", "vagrant_network", "--subnet", "172.20.0.0/16")
subject.create_network("vagrant_network", opts)
end
end
describe '#docker_disconnet_network' do
it 'disconnects a network from a container' do
expect(subject).to receive(:execute).with("docker", "network", "disconnect", "vagrant_network", cid, "--force")
subject.disconnect_network("vagrant_network", cid)
end
end
describe '#docker_inspect_network' do
it 'gets info about a network' do
expect(subject).to receive(:execute).with("docker", "network", "inspect", "vagrant_network")
subject.inspect_network("vagrant_network")
end
end
describe '#docker_list_network' do
it 'lists docker networks' do
expect(subject).to receive(:execute).with("docker", "network", "ls")
subject.list_network()
end
end
describe '#docker_rm_network' do
it 'deletes a docker network' do
expect(subject).to receive(:execute).with("docker", "network", "rm", "vagrant_network")
subject.rm_network("vagrant_network")
end
end
describe '#network_defined?' do
let(:subnet_string) { "172.20.0.0/16" }
let(:network_names) { ["vagrant_network_172.20.0.0/16", "bridge", "null" ] }
it "returns network name if defined" do
allow(subject).to receive(:list_network_names).and_return(network_names)
allow(subject).to receive(:inspect_network).and_return(JSON.load(docker_network_struct))
network_name = subject.network_defined?(subnet_string)
expect(network_name).to eq("vagrant_network_172.20.0.0/16")
end
it "returns nil name if not defined" do
allow(subject).to receive(:list_network_names).and_return(network_names)
allow(subject).to receive(:inspect_network).and_return(JSON.load(docker_network_struct))
network_name = subject.network_defined?("120.20.0.0/24")
expect(network_name).to eq(nil)
end
end
describe '#network_containing_address' do
let(:address) { "172.20.128.2" }
let(:network_names) { ["vagrant_network_172.20.0.0/16", "bridge", "null" ] }
it "returns the network name if it contains the requested address" do
allow(subject).to receive(:list_network_names).and_return(network_names)
allow(subject).to receive(:inspect_network).and_return(JSON.load(docker_network_struct))
network_name = subject.network_containing_address(address)
expect(network_name).to eq("vagrant_network_172.20.0.0/16")
end
it "returns nil if no networks contain the requested address" do
allow(subject).to receive(:list_network_names).and_return(network_names)
allow(subject).to receive(:inspect_network).and_return(JSON.load(docker_network_struct))
network_name = subject.network_containing_address("127.0.0.1")
expect(network_name).to eq(nil)
end
end
describe '#existing_named_network?' do
let(:network_names) { ["vagrant_network_172.20.0.0/16", "bridge", "null" ] }
it "returns true if the network exists" do
allow(subject).to receive(:list_network_names).and_return(network_names)
expect(subject.existing_named_network?("vagrant_network_172.20.0.0/16")).to be_truthy
end
it "returns false if the network does not exist" do
allow(subject).to receive(:list_network_names).and_return(network_names)
expect(subject.existing_named_network?("vagrant_network_17.0.0/16")).to be_falsey
end
end
describe '#list_network_names' do
let(:unparsed_network_names) { "vagrant_network_172.20.0.0/16\nbridge\nnull" }
let(:network_names) { ["vagrant_network_172.20.0.0/16", "bridge", "null" ] }
it "lists the network names" do
allow(subject).to receive(:list_network).with("--format={{.Name}}").
and_return(unparsed_network_names)
expect(subject.list_network_names).to eq(network_names)
end
end
describe '#network_used?' do
let(:network_name) { "vagrant_network_172.20.0.0/16" }
it "returns nil if no networks" do
allow(subject).to receive(:inspect_network).with(network_name).and_return(nil)
expect(subject.network_used?(network_name)).to eq(nil)
end
it "returns true if network has containers in use" do
allow(subject).to receive(:inspect_network).with(network_name).and_return([JSON.load(docker_network_struct).last])
expect(subject.network_used?(network_name)).to be_truthy
end
it "returns false if network has containers in use" do
allow(subject).to receive(:inspect_network).with("host").and_return([JSON.load(docker_network_struct)[1]])
expect(subject.network_used?("host")).to be_falsey
end
end
end