Merge pull request #10702 from briancain/docker-network-support
Docker Provider Network Support
This commit is contained in:
commit
ec67151312
|
@ -161,6 +161,7 @@ module VagrantPlugins
|
|||
b4.use action_halt
|
||||
b4.use HostMachineSyncFoldersDisable
|
||||
b4.use Destroy
|
||||
b4.use DestroyNetwork
|
||||
b4.use DestroyBuildImage
|
||||
else
|
||||
b4.use Message,
|
||||
|
@ -243,6 +244,7 @@ module VagrantPlugins
|
|||
b2.use PrepareNFSValidIds
|
||||
b2.use SyncedFolderCleanup
|
||||
b2.use PrepareNFSSettings
|
||||
b2.use PrepareNetworks
|
||||
b2.use Login
|
||||
b2.use Build
|
||||
|
||||
|
@ -265,6 +267,7 @@ module VagrantPlugins
|
|||
end
|
||||
end
|
||||
|
||||
b2.use ConnectNetworks
|
||||
b2.use Start
|
||||
b2.use WaitForRunning
|
||||
|
||||
|
@ -292,9 +295,11 @@ module VagrantPlugins
|
|||
action_root = Pathname.new(File.expand_path("../action", __FILE__))
|
||||
autoload :Build, action_root.join("build")
|
||||
autoload :CompareSyncedFolders, action_root.join("compare_synced_folders")
|
||||
autoload :ConnectNetworks, action_root.join("connect_networks")
|
||||
autoload :Create, action_root.join("create")
|
||||
autoload :Destroy, action_root.join("destroy")
|
||||
autoload :DestroyBuildImage, action_root.join("destroy_build_image")
|
||||
autoload :DestroyNetwork, action_root.join("destroy_network")
|
||||
autoload :ForwardedPorts, action_root.join("forwarded_ports")
|
||||
autoload :HasSSH, action_root.join("has_ssh")
|
||||
autoload :HostMachine, action_root.join("host_machine")
|
||||
|
@ -308,12 +313,13 @@ module VagrantPlugins
|
|||
autoload :IsBuild, action_root.join("is_build")
|
||||
autoload :IsHostMachineCreated, action_root.join("is_host_machine_created")
|
||||
autoload :Login, action_root.join("login")
|
||||
autoload :Pull, action_root.join("pull")
|
||||
autoload :PrepareSSH, action_root.join("prepare_ssh")
|
||||
autoload :Stop, action_root.join("stop")
|
||||
autoload :PrepareNetworks, action_root.join("prepare_networks")
|
||||
autoload :PrepareNFSValidIds, action_root.join("prepare_nfs_valid_ids")
|
||||
autoload :PrepareNFSSettings, action_root.join("prepare_nfs_settings")
|
||||
autoload :PrepareSSH, action_root.join("prepare_ssh")
|
||||
autoload :Pull, action_root.join("pull")
|
||||
autoload :Start, action_root.join("start")
|
||||
autoload :Stop, action_root.join("stop")
|
||||
autoload :WaitForRunning, action_root.join("wait_for_running")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
require 'ipaddr'
|
||||
require 'log4r'
|
||||
|
||||
require 'vagrant/util/scoped_hash_override'
|
||||
|
||||
module VagrantPlugins
|
||||
module DockerProvider
|
||||
module Action
|
||||
class ConnectNetworks
|
||||
|
||||
include Vagrant::Util::ScopedHashOverride
|
||||
|
||||
def initialize(app, env)
|
||||
@app = app
|
||||
@logger = Log4r::Logger.new('vagrant::plugins::docker::connectnetworks')
|
||||
end
|
||||
|
||||
# Generate CLI arguments for creating the docker network.
|
||||
#
|
||||
# @param [Hash] options Options from the network config
|
||||
# @returns[Array<String> Network create arguments
|
||||
def generate_connect_cli_arguments(options)
|
||||
options.map do |key, value|
|
||||
# If value is false, option is not set
|
||||
next if value.to_s == "false"
|
||||
# If value is true, consider feature flag with no value
|
||||
opt = value.to_s == "true" ? [] : [value]
|
||||
opt.unshift("--#{key.to_s.tr("_", "-")}")
|
||||
end.flatten.compact
|
||||
end
|
||||
|
||||
# Execute the action
|
||||
def call(env)
|
||||
# If we are using a host VM, then don't worry about it
|
||||
machine = env[:machine]
|
||||
if machine.provider.host_vm?
|
||||
@logger.debug("Not setting up networks because docker host_vm is in use")
|
||||
return @app.call(env)
|
||||
end
|
||||
|
||||
env[:ui].info(I18n.t("docker_provider.network_connect"))
|
||||
|
||||
connections = env[:docker_connects] || {}
|
||||
|
||||
machine.config.vm.networks.each_with_index do |args, idx|
|
||||
type, options = args
|
||||
next if type != :private_network && type != :public_network
|
||||
|
||||
network_options = scoped_hash_override(options, :docker_connect)
|
||||
network_options.delete_if{|k,_| options.key?(k)}
|
||||
network_name = connections[idx]
|
||||
|
||||
if !network_name
|
||||
raise Errors::NetworkNameMissing,
|
||||
index: idx,
|
||||
container: machine.name
|
||||
end
|
||||
|
||||
@logger.debug("Connecting network #{network_name} to container guest #{machine.name}")
|
||||
if options[:ip] && options[:type] != "dhcp"
|
||||
if IPAddr.new(options[:ip]).ipv4?
|
||||
network_options[:ip] = options[:ip]
|
||||
else
|
||||
network_options[:ip6] = options[:ip]
|
||||
end
|
||||
end
|
||||
network_options[:alias] = options[:alias] if options[:alias]
|
||||
connect_opts = generate_connect_cli_arguments(network_options)
|
||||
machine.provider.driver.connect_network(network_name, machine.id, connect_opts)
|
||||
end
|
||||
|
||||
@app.call(env)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,50 @@
|
|||
require 'log4r'
|
||||
|
||||
module VagrantPlugins
|
||||
module DockerProvider
|
||||
module Action
|
||||
class DestroyNetwork
|
||||
|
||||
@@lock = Mutex.new
|
||||
|
||||
def initialize(app, env)
|
||||
@app = app
|
||||
@logger = Log4r::Logger.new('vagrant::plugins::docker::network')
|
||||
end
|
||||
|
||||
def call(env)
|
||||
# If we are using a host VM, then don't worry about it
|
||||
machine = env[:machine]
|
||||
if machine.provider.host_vm?
|
||||
@logger.debug("Not setting up networks because docker host_vm is in use")
|
||||
return @app.call(env)
|
||||
end
|
||||
|
||||
@@lock.synchronize do
|
||||
machine.env.lock("docker-network-destroy", retry: true) do
|
||||
machine.config.vm.networks.each do |type, options|
|
||||
next if type != :private_network && type != :public_network
|
||||
|
||||
vagrant_networks = machine.provider.driver.list_network_names.find_all do |n|
|
||||
n.start_with?("vagrant_network")
|
||||
end
|
||||
|
||||
vagrant_networks.each do |network_name|
|
||||
if machine.provider.driver.existing_named_network?(network_name) &&
|
||||
!machine.provider.driver.network_used?(network_name)
|
||||
env[:ui].info(I18n.t("docker_provider.network_destroy", network_name: network_name))
|
||||
machine.provider.driver.rm_network(network_name)
|
||||
else
|
||||
@logger.debug("Network #{network_name} not found or in use")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@app.call(env)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,355 @@
|
|||
require 'ipaddr'
|
||||
require 'log4r'
|
||||
|
||||
require 'vagrant/util/scoped_hash_override'
|
||||
|
||||
module VagrantPlugins
|
||||
module DockerProvider
|
||||
module Action
|
||||
class PrepareNetworks
|
||||
|
||||
include Vagrant::Util::ScopedHashOverride
|
||||
|
||||
@@lock = Mutex.new
|
||||
|
||||
def initialize(app, env)
|
||||
@app = app
|
||||
@logger = Log4r::Logger.new('vagrant::plugins::docker::preparenetworks')
|
||||
end
|
||||
|
||||
# Generate CLI arguments for creating the docker network.
|
||||
#
|
||||
# @param [Hash] options Options from the network config
|
||||
# @returns[Array<String>] Network create arguments
|
||||
def generate_create_cli_arguments(options)
|
||||
options.map do |key, value|
|
||||
# If value is false, option is not set
|
||||
next if value.to_s == "false"
|
||||
# If value is true, consider feature flag with no value
|
||||
opt = value.to_s == "true" ? [] : [value]
|
||||
opt.unshift("--#{key.to_s.tr("_", "-")}")
|
||||
end.flatten.compact
|
||||
end
|
||||
|
||||
# @return [Array<Socket::Ifaddr>] interface list
|
||||
def list_interfaces
|
||||
Socket.getifaddrs.find_all do |i|
|
||||
i.addr.ip? && !i.addr.ipv4_loopback? &&
|
||||
!i.addr.ipv6_loopback? && !i.addr.ipv6_linklocal?
|
||||
end
|
||||
end
|
||||
|
||||
# Validates that a network name exists. If it does not
|
||||
# exist, an exception is raised.
|
||||
#
|
||||
# @param [String] network_name Name of existing network
|
||||
# @param [Hash] env Local call env
|
||||
# @return [Boolean]
|
||||
def validate_network_name!(network_name, env)
|
||||
if !env[:machine].provider.driver.existing_named_network?(network_name)
|
||||
raise Errors::NetworkNameUndefined,
|
||||
network_name: network_name
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
# Validates that the provided options are compatible with a
|
||||
# pre-existing network. Raises exceptions on invalid configurations
|
||||
#
|
||||
# @param [String] network_name Name of the network
|
||||
# @param [Hash] root_options Root networking options
|
||||
# @param [Hash] network_options Docker scoped networking options
|
||||
# @param [Driver] driver Docker driver
|
||||
# @return [Boolean]
|
||||
def validate_network_configuration!(network_name, root_options, network_options, driver)
|
||||
if root_options[:ip] &&
|
||||
driver.network_containing_address(root_options[:ip]) != network_name
|
||||
raise Errors::NetworkAddressInvalid,
|
||||
address: root_options[:ip],
|
||||
network_name: network_name
|
||||
end
|
||||
if network_options[:subnet] &&
|
||||
driver.network_containing_address(network_options[:subnet]) != network_name
|
||||
raise Errors::NetworkSubnetInvalid,
|
||||
subnet: network_options[:subnet],
|
||||
network_name: network_name
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
# Generate configuration for private network
|
||||
#
|
||||
# @param [Hash] root_options Root networking options
|
||||
# @param [Hash] net_options Docker scoped networking options
|
||||
# @param [Hash] env Local call env
|
||||
# @return [String, Hash] Network name and updated network_options
|
||||
def process_private_network(root_options, network_options, env)
|
||||
if root_options[:name] && validate_network_name!(root_options[:name], env)
|
||||
network_name = root_options[:name]
|
||||
end
|
||||
|
||||
if root_options[:type].to_s == "dhcp"
|
||||
if !root_options[:ip] && !root_options[:subnet]
|
||||
network_name = "vagrant_network" if !network_name
|
||||
return [network_name, network_options]
|
||||
end
|
||||
if root_options[:subnet]
|
||||
addr = IPAddr.new(root_options[:subnet])
|
||||
root_options[:netmask] = addr.prefix
|
||||
end
|
||||
end
|
||||
|
||||
if root_options[:ip]
|
||||
addr = IPAddr.new(root_options[:ip])
|
||||
elsif addr.nil?
|
||||
raise Errors::NetworkIPAddressRequired
|
||||
end
|
||||
|
||||
# If address is ipv6, enable ipv6 support
|
||||
network_options[:ipv6] = addr.ipv6?
|
||||
|
||||
# If no mask is provided, attempt to locate any existing
|
||||
# network which contains the assigned IP address
|
||||
if !root_options[:netmask] && !network_name
|
||||
network_name = env[:machine].provider.driver.
|
||||
network_containing_address(root_options[:ip])
|
||||
# When no existing network is found, we are creating
|
||||
# a new network. Since no mask was provided, default
|
||||
# to /24 for ipv4 and /64 for ipv6
|
||||
if !network_name
|
||||
root_options[:netmask] = addr.ipv4? ? 24 : 64
|
||||
end
|
||||
end
|
||||
|
||||
# With no network name, process options to find or determine
|
||||
# name for new network
|
||||
if !network_name
|
||||
if !root_options[:subnet]
|
||||
# Only generate a subnet if not given one
|
||||
subnet = IPAddr.new("#{addr}/#{root_options[:netmask]}")
|
||||
network = "#{subnet}/#{root_options[:netmask]}"
|
||||
else
|
||||
network = root_options[:subnet]
|
||||
end
|
||||
|
||||
network_options[:subnet] = network
|
||||
existing_network = env[:machine].provider.driver.
|
||||
network_defined?(network)
|
||||
|
||||
if !existing_network
|
||||
network_name = "vagrant_network_#{network}"
|
||||
else
|
||||
if !existing_network.to_s.start_with?("vagrant_network")
|
||||
env[:ui].warn(I18n.t("docker_provider.subnet_exists",
|
||||
network_name: existing_network,
|
||||
subnet: network))
|
||||
end
|
||||
network_name = existing_network
|
||||
end
|
||||
end
|
||||
|
||||
[network_name, network_options]
|
||||
end
|
||||
|
||||
# Generate configuration for public network
|
||||
#
|
||||
# @param [Hash] root_options Root networking options
|
||||
# @param [Hash] net_options Docker scoped networking options
|
||||
# @param [Hash] env Local call env
|
||||
# @return [String, Hash] Network name and updated network_options
|
||||
def process_public_network(root_options, net_options, env)
|
||||
if root_options[:name] && validate_network_name!(root_options[:name], env)
|
||||
network_name = root_options[:name]
|
||||
end
|
||||
if !network_name
|
||||
valid_interfaces = list_interfaces
|
||||
if valid_interfaces.empty?
|
||||
raise Errors::NetworkNoInterfaces
|
||||
elsif valid_interfaces.size == 1
|
||||
bridge_interface = valid_interfaces.first
|
||||
elsif i = valid_interfaces.detect{|i| Array(root_options[:bridge]).include?(i.name) }
|
||||
bridge_interface = i
|
||||
end
|
||||
if !bridge_interface
|
||||
env[:ui].info(I18n.t("vagrant.actions.vm.bridged_networking.available"),
|
||||
prefix: false)
|
||||
valid_interfaces.each_with_index do |int, i|
|
||||
env[:ui].info("#{i + 1}) #{int.name}", prefix: false)
|
||||
end
|
||||
env[:ui].info(I18n.t(
|
||||
"vagrant.actions.vm.bridged_networking.choice_help") + "\n",
|
||||
prefix: false
|
||||
)
|
||||
end
|
||||
while !bridge_interface
|
||||
choice = env[:ui].ask(
|
||||
I18n.t("vagrant.actions.vm.bridged_networking.select_interface") + " ",
|
||||
prefix: false)
|
||||
bridge_interface = valid_interfaces[choice.to_i - 1]
|
||||
end
|
||||
base_opts = Vagrant::Util::HashWithIndifferentAccess.new
|
||||
base_opts[:opt] = "parent=#{bridge_interface.name}"
|
||||
subnet = IPAddr.new(bridge_interface.addr.ip_address <<
|
||||
"/" << bridge_interface.netmask.ip_unpack.first)
|
||||
base_opts[:subnet] = "#{subnet}/#{subnet.prefix}"
|
||||
subnet_addr = IPAddr.new(base_opts[:subnet])
|
||||
base_opts[:driver] = "macvlan"
|
||||
base_opts[:gateway] = subnet_addr.succ.to_s
|
||||
base_opts[:ipv6] = subnet_addr.ipv6?
|
||||
network_options = base_opts.merge(net_options)
|
||||
|
||||
# Check if network already exists for this subnet
|
||||
network_name = env[:machine].provider.driver.
|
||||
network_containing_address(network_options[:gateway])
|
||||
if !network_name
|
||||
network_name = "vagrant_network_public_#{bridge_interface.name}"
|
||||
end
|
||||
|
||||
# If the network doesn't already exist, gather available address range
|
||||
# within subnet which docker can provide addressing
|
||||
if !env[:machine].provider.driver.existing_named_network?(network_name)
|
||||
if !net_options[:gateway]
|
||||
network_options[:gateway] = request_public_gateway(
|
||||
network_options, bridge_interface.name, env)
|
||||
end
|
||||
network_options[:ip_range] = request_public_iprange(
|
||||
network_options, bridge_interface.name, env)
|
||||
end
|
||||
end
|
||||
[network_name, network_options]
|
||||
end
|
||||
|
||||
# Request the gateway address for the public network
|
||||
#
|
||||
# @param [Hash] network_options Docker scoped networking options
|
||||
# @param [String] interface The bridge interface used
|
||||
# @param [Hash] env Local call env
|
||||
# @return [String] Gateway address
|
||||
def request_public_gateway(network_options, interface, env)
|
||||
subnet = IPAddr.new(network_options[:subnet])
|
||||
gateway = nil
|
||||
while !gateway
|
||||
gateway = env[:ui].ask(I18n.t(
|
||||
"docker_provider.network_bridge_gateway_request",
|
||||
interface: interface,
|
||||
default_gateway: network_options[:gateway]) + " ",
|
||||
prefix: false
|
||||
).strip
|
||||
if gateway.empty?
|
||||
gateway = network_options[:gateway]
|
||||
end
|
||||
begin
|
||||
gateway = IPAddr.new(gateway)
|
||||
if !subnet.include?(gateway)
|
||||
env[:ui].warn(I18n.t("docker_provider.network_bridge_gateway_outofbounds",
|
||||
gateway: gateway,
|
||||
subnet: network_options[:subnet]) + "\n", prefix: false)
|
||||
end
|
||||
rescue IPAddr::InvalidAddressError
|
||||
env[:ui].warn(I18n.t("docker_provider.network_bridge_gateway_invalid",
|
||||
gateway: gateway) + "\n", prefix: false)
|
||||
gateway = nil
|
||||
end
|
||||
end
|
||||
gateway.to_s
|
||||
end
|
||||
|
||||
# Request the IP range allowed for use by docker when creating a new
|
||||
# public network
|
||||
#
|
||||
# @param [Hash] network_options Docker scoped networking options
|
||||
# @param [String] interface The bridge interface used
|
||||
# @param [Hash] env Local call env
|
||||
# @return [String] Address range
|
||||
def request_public_iprange(network_options, interface, env)
|
||||
return network_options[:ip_range] if network_options[:ip_range]
|
||||
subnet = IPAddr.new(network_options[:subnet])
|
||||
env[:ui].info(I18n.t(
|
||||
"docker_provider.network_bridge_iprange_info") + "\n",
|
||||
prefix: false
|
||||
)
|
||||
range = nil
|
||||
while !range
|
||||
range = env[:ui].ask(I18n.t(
|
||||
"docker_provider.network_bridge_iprange_request",
|
||||
interface: interface,
|
||||
default_range: network_options[:subnet]) + " ",
|
||||
prefix: false
|
||||
).strip
|
||||
if range.empty?
|
||||
range = network_options[:subnet]
|
||||
end
|
||||
begin
|
||||
range = IPAddr.new(range)
|
||||
if !subnet.include?(range)
|
||||
puts "we in here"
|
||||
env[:ui].warn(I18n.t(
|
||||
"docker_provider.network_bridge_iprange_outofbounds",
|
||||
subnet: network_options[:subnet],
|
||||
range: "#{range}/#{range.prefix}"
|
||||
) + "\n", prefix: false)
|
||||
range = nil
|
||||
end
|
||||
rescue IPAddr::InvalidAddressError
|
||||
env[:ui].warn(I18n.t(
|
||||
"docker_provider.network_bridge_iprange_invalid",
|
||||
range: range) + "\n", prefix: false)
|
||||
range = nil
|
||||
end
|
||||
end
|
||||
"#{range}/#{range.prefix}"
|
||||
end
|
||||
|
||||
# Execute the action
|
||||
def call(env)
|
||||
# If we are using a host VM, then don't worry about it
|
||||
machine = env[:machine]
|
||||
if machine.provider.host_vm?
|
||||
@logger.debug("Not setting up networks because docker host_vm is in use")
|
||||
return @app.call(env)
|
||||
end
|
||||
|
||||
connections = {}
|
||||
@@lock.synchronize do
|
||||
machine.env.lock("docker-network-create", retry: true) do
|
||||
env[:ui].info(I18n.t("docker_provider.network_create"))
|
||||
machine.config.vm.networks.each_with_index do |net_info, net_idx|
|
||||
type, options = net_info
|
||||
network_options = scoped_hash_override(options, :docker_network)
|
||||
network_options.delete_if{|k,_| options.key?(k)}
|
||||
|
||||
case type
|
||||
when :public_network
|
||||
network_name, network_options = process_public_network(
|
||||
options, network_options, env)
|
||||
when :private_network
|
||||
network_name, network_options = process_private_network(
|
||||
options, network_options, env)
|
||||
else
|
||||
next # unsupported type so ignore
|
||||
end
|
||||
|
||||
if !network_name
|
||||
raise Errors::NetworkInvalidOption, container: machine.name
|
||||
end
|
||||
|
||||
if !machine.provider.driver.existing_named_network?(network_name)
|
||||
@logger.debug("Creating network #{network_name}")
|
||||
cli_opts = generate_create_cli_arguments(network_options)
|
||||
machine.provider.driver.create_network(network_name, cli_opts)
|
||||
else
|
||||
@logger.debug("Network #{network_name} already created")
|
||||
validate_network_configuration!(network_name, options, network_options, machine.provider.driver)
|
||||
end
|
||||
connections[net_idx] = network_name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
env[:docker_connects] = connections
|
||||
@app.call(env)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -152,21 +152,25 @@ module VagrantPlugins
|
|||
def rmi(id)
|
||||
execute('docker', 'rmi', id)
|
||||
return true
|
||||
rescue Exception => e
|
||||
rescue => e
|
||||
return false if e.to_s.include?("is using it")
|
||||
raise if !e.to_s.include?("No such image")
|
||||
end
|
||||
|
||||
# Inspect the provided container
|
||||
#
|
||||
# @param [String] cid ID or name of container
|
||||
# @return [Hash]
|
||||
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
|
||||
JSON.parse(execute('docker', 'inspect', cid)).first
|
||||
end
|
||||
|
||||
# @return [Array<String>] list of all container IDs
|
||||
def all_containers
|
||||
execute('docker', 'ps', '-a', '-q', '--no-trunc').to_s.split
|
||||
end
|
||||
|
||||
# @return [String] IP address of the docker bridge
|
||||
def docker_bridge_ip
|
||||
output = execute('/sbin/ip', '-4', 'addr', 'show', 'scope', 'global', 'docker0')
|
||||
if output =~ /^\s+inet ([0-9.]+)\/[0-9]+\s+/
|
||||
|
@ -177,9 +181,149 @@ module VagrantPlugins
|
|||
end
|
||||
end
|
||||
|
||||
# @param [String] network - name of network to connect conatiner to
|
||||
# @param [String] cid - container id
|
||||
# @param [Array] opts - An array of flags used for listing networks
|
||||
def connect_network(network, cid, opts=nil)
|
||||
command = ['docker', 'network', 'connect', network, cid].push(*opts)
|
||||
output = execute(*command)
|
||||
output
|
||||
end
|
||||
|
||||
# @param [String] network - name of network to create
|
||||
# @param [Array] opts - An array of flags used for listing networks
|
||||
def create_network(network, opts=nil)
|
||||
command = ['docker', 'network', 'create', network].push(*opts)
|
||||
output = execute(*command)
|
||||
output
|
||||
end
|
||||
|
||||
# @param [String] network - name of network to disconnect container from
|
||||
# @param [String] cid - container id
|
||||
def disconnect_network(network, cid)
|
||||
command = ['docker', 'network', 'disconnect', network, cid, "--force"]
|
||||
output = execute(*command)
|
||||
output
|
||||
end
|
||||
|
||||
# @param [Array] networks - list of networks to inspect
|
||||
# @param [Array] opts - An array of flags used for listing networks
|
||||
def inspect_network(network, opts=nil)
|
||||
command = ['docker', 'network', 'inspect'] + Array(network)
|
||||
command = command.push(*opts)
|
||||
output = execute(*command)
|
||||
begin
|
||||
JSON.load(output)
|
||||
rescue JSON::ParserError
|
||||
@logger.warn("Failed to parse network inspection of network: #{network}")
|
||||
@logger.debug("Failed network output content: `#{output.inspect}`")
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
# @param [String] opts - Flags used for listing networks
|
||||
def list_network(*opts)
|
||||
command = ['docker', 'network', 'ls', *opts]
|
||||
output = execute(*command)
|
||||
output
|
||||
end
|
||||
|
||||
# Will delete _all_ defined but unused networks in the docker engine. Even
|
||||
# networks not created by Vagrant.
|
||||
#
|
||||
# @param [Array] opts - An array of flags used for listing networks
|
||||
def prune_network(opts=nil)
|
||||
command = ['docker', 'network', 'prune', '--force'].push(*opts)
|
||||
output = execute(*command)
|
||||
output
|
||||
end
|
||||
|
||||
# Delete network(s)
|
||||
#
|
||||
# @param [String] network - name of network to remove
|
||||
def rm_network(*network)
|
||||
command = ['docker', 'network', 'rm', *network]
|
||||
output = execute(*command)
|
||||
output
|
||||
end
|
||||
|
||||
# @param [Array] opts - An array of flags used for listing networks
|
||||
def execute(*cmd, **opts, &block)
|
||||
@executor.execute(*cmd, **opts, &block)
|
||||
end
|
||||
|
||||
# ######################
|
||||
# Docker network helpers
|
||||
# ######################
|
||||
|
||||
# Determines if a given network has been defined through vagrant with a given
|
||||
# subnet string
|
||||
#
|
||||
# @param [String] subnet_string - Subnet to look for
|
||||
# @return [String] network name - Name of network with requested subnet.`nil` if not found
|
||||
def network_defined?(subnet_string)
|
||||
all_networks = list_network_names
|
||||
|
||||
network_info = inspect_network(all_networks)
|
||||
network_info.each do |network|
|
||||
config = network["IPAM"]["Config"]
|
||||
if (config.size > 0 &&
|
||||
config.first["Subnet"] == subnet_string)
|
||||
@logger.debug("Found existing network #{network["Name"]} already configured with #{subnet_string}")
|
||||
return network["Name"]
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
# Locate network which contains given address
|
||||
#
|
||||
# @param [String] address IP address
|
||||
# @return [String] network name
|
||||
def network_containing_address(address)
|
||||
names = list_network_names
|
||||
networks = inspect_network(names)
|
||||
return if !networks
|
||||
networks.each do |net|
|
||||
next if !net["IPAM"]
|
||||
config = net["IPAM"]["Config"]
|
||||
next if !config || config.size < 1
|
||||
config.each do |opts|
|
||||
subnet = IPAddr.new(opts["Subnet"])
|
||||
if subnet.include?(address)
|
||||
return net["Name"]
|
||||
end
|
||||
end
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
# Looks to see if a docker network has already been defined
|
||||
# with the given name
|
||||
#
|
||||
# @param [String] network_name - name of network to look for
|
||||
# @return [Bool]
|
||||
def existing_named_network?(network_name)
|
||||
result = list_network_names
|
||||
result.any?{|net_name| net_name == network_name}
|
||||
end
|
||||
|
||||
# @return [Array<String>] list of all docker networks
|
||||
def list_network_names
|
||||
list_network("--format={{.Name}}").split("\n").map(&:strip)
|
||||
end
|
||||
|
||||
# Returns true or false if network is in use or not.
|
||||
# Nil if Vagrant fails to receive proper JSON from `docker network inspect`
|
||||
#
|
||||
# @param [String] network - name of network to look for
|
||||
# @return [Bool,nil]
|
||||
def network_used?(network)
|
||||
result = inspect_network(network)
|
||||
return nil if !result
|
||||
return result.first["Containers"].size > 0
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -45,6 +45,34 @@ module VagrantPlugins
|
|||
error_key(:docker_provider_nfs_without_privileged)
|
||||
end
|
||||
|
||||
class NetworkAddressInvalid < DockerError
|
||||
error_key(:network_address_invalid)
|
||||
end
|
||||
|
||||
class NetworkIPAddressRequired < DockerError
|
||||
error_key(:network_address_required)
|
||||
end
|
||||
|
||||
class NetworkSubnetInvalid < DockerError
|
||||
error_key(:network_subnet_invalid)
|
||||
end
|
||||
|
||||
class NetworkInvalidOption < DockerError
|
||||
error_key(:network_invalid_option)
|
||||
end
|
||||
|
||||
class NetworkNameMissing < DockerError
|
||||
error_key(:network_name_missing)
|
||||
end
|
||||
|
||||
class NetworkNameUndefined < DockerError
|
||||
error_key(:network_name_undefined)
|
||||
end
|
||||
|
||||
class NetworkNoInterfaces < DockerError
|
||||
error_key(:network_no_interfaces)
|
||||
end
|
||||
|
||||
class PackageNotSupported < DockerError
|
||||
error_key(:package_not_supported)
|
||||
end
|
||||
|
|
|
@ -2148,6 +2148,8 @@ en:
|
|||
choice_help: |-
|
||||
When choosing an interface, it is usually the one that is
|
||||
being used to connect to the internet.
|
||||
select_interface: |-
|
||||
Which interface should the network bridge to?
|
||||
specific_not_found: |-
|
||||
Specific bridge '%{bridge}' not found. You may be asked to specify
|
||||
which network to bridge to.
|
||||
|
|
|
@ -45,6 +45,35 @@ en:
|
|||
This container requires a host VM, and the state of that VM
|
||||
is unknown. Run `vagrant up` to verify that the container and
|
||||
its host VM is running, then try again.
|
||||
network_bridge_gateway_invalid: |-
|
||||
The provided gateway IP address is invalid (%{gateway}). Please
|
||||
provide a valid IP address.
|
||||
network_bridge_gateway_outofbounds: |-
|
||||
The provided gateway IP (%{gateway}) is not within the defined
|
||||
subnet (%{subnet}). Please provide an IP address within the
|
||||
defined subnet.
|
||||
network_bridge_gateway_request: |-
|
||||
Gateway IP address for %{interface} interface [%{default_gateway}]:
|
||||
network_bridge_iprange_info: |-
|
||||
When an explicit address is not provided to a container attached
|
||||
to this bridged network, docker will supply an address to the
|
||||
container. This is independent of the local DHCP service that
|
||||
may be available on the network.
|
||||
network_bridge_iprange_invalid: |-
|
||||
The provided IP address range is invalid (%{range}). Please
|
||||
provide a valid range.
|
||||
network_bridge_iprange_outofbounds: |-
|
||||
The provided IP address range (%{range}) is not within the
|
||||
defined subnet (%{subnet}). Please provide an address range
|
||||
within the defined subnet.
|
||||
network_bridge_iprange_request: |-
|
||||
Available address range for assignment on %{interface} interface [%{default_range}]:
|
||||
network_create: |-
|
||||
Creating and configuring docker networks...
|
||||
network_connect: |-
|
||||
Enabling network interfaces...
|
||||
network_destroy: |-
|
||||
Removing network %{network_name} ...
|
||||
not_created_skip: |-
|
||||
Container not created. Skipping.
|
||||
not_docker_provider: |-
|
||||
|
@ -66,6 +95,9 @@ en:
|
|||
ssh_through_host_vm: |-
|
||||
SSH will be proxied through the Docker virtual machine since we're
|
||||
not running Docker natively. This is just a notice, and not an error.
|
||||
subnet_exists: |-
|
||||
A network called '%{network_name}' using subnet '%{subnet}' is already in use.
|
||||
Using '%{network_name}' instead of creating a new network...
|
||||
synced_folders_changed: |-
|
||||
Vagrant has noticed that the synced folder definitions have changed.
|
||||
With Docker, these synced folder changes won't take effect until you
|
||||
|
@ -197,6 +229,38 @@ en:
|
|||
is functional and properly configured.
|
||||
|
||||
Host VM ID: %{id}
|
||||
network_address_invalid: |-
|
||||
The configured network address is not valid within the configured
|
||||
subnet of the defined network. Please update the network settings
|
||||
and try again.
|
||||
|
||||
Configured address: %{address}
|
||||
Network name: %{network_name}
|
||||
network_address_required: |-
|
||||
An IP address is required if not using `type: "dhcp"` or not specifying a `subnet`.
|
||||
network_invalid_option: |-
|
||||
Invalid option given for docker network for guest "%{container}". Must specify either
|
||||
a `subnet` or use `type: "dhcp"`.
|
||||
network_name_missing: |-
|
||||
The Docker provider is unable to connect the container to the
|
||||
defined network due to a missing network name. Please validate
|
||||
your configuration and try again.
|
||||
|
||||
Container: %{container}
|
||||
Network Number: %{index}
|
||||
network_name_undefined: |-
|
||||
The Docker provider was unable to configure networking using the
|
||||
provided network name `%{network_name}`. Please ensure the network
|
||||
name is correct and exists, then try again.
|
||||
network_no_interfaces: |-
|
||||
The Docker provider was unable to list any available interfaces to bridge
|
||||
the public network with.
|
||||
network_subnet_invalid: |-
|
||||
The configured network subnet is not valid for the defined network.
|
||||
Please update the network settings and try again.
|
||||
|
||||
Configured subnet: %{subnet}
|
||||
Network name: %{network_name}
|
||||
package_not_supported: |-
|
||||
The "package" command is not supported with the Docker provider.
|
||||
If you'd like to commit or push your Docker container, please SSH
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
require_relative "../../../../base"
|
||||
require_relative "../../../../../../plugins/providers/docker/action/connect_networks"
|
||||
|
||||
|
||||
describe VagrantPlugins::DockerProvider::Action::ConnectNetworks do
|
||||
include_context "unit"
|
||||
include_context "virtualbox"
|
||||
|
||||
let(:sandbox) { isolated_environment }
|
||||
|
||||
let(:iso_env) do
|
||||
# We have to create a Vagrantfile so there is a root path
|
||||
sandbox.vagrantfile("")
|
||||
sandbox.create_vagrant_env
|
||||
end
|
||||
|
||||
let(:machine) do
|
||||
iso_env.machine(iso_env.machine_names[0], :docker).tap do |m|
|
||||
allow(m).to receive(:id).and_return("12345")
|
||||
allow(m.provider).to receive(:driver).and_return(driver)
|
||||
allow(m.provider).to receive(:host_vm?).and_return(false)
|
||||
allow(m.config.vm).to receive(:networks).and_return(networks)
|
||||
end
|
||||
end
|
||||
|
||||
let(:docker_connects) { {0=>"vagrant_network_172.20.0.0/16", 1=>"vagrant_network_public_wlp4s0", 2=>"vagrant_network_2a02:6b8:b010:9020:1::/80"} }
|
||||
|
||||
let(:env) {{ machine: machine, ui: machine.ui, root_path: Pathname.new("."),
|
||||
docker_connects: docker_connects }}
|
||||
let(:app) { lambda { |*args| }}
|
||||
let(:driver) { double("driver", create: "abcd1234") }
|
||||
|
||||
let(:networks) { [[:private_network,
|
||||
{:ip=>"172.20.128.2",
|
||||
:subnet=>"172.20.0.0/16",
|
||||
:driver=>"bridge",
|
||||
:internal=>"true",
|
||||
:alias=>"mynetwork",
|
||||
:protocol=>"tcp",
|
||||
:id=>"80e017d5-388f-4a2f-a3de-f8dce8156a58"}],
|
||||
[:public_network,
|
||||
{:ip=>"172.30.130.2",
|
||||
:subnet=>"172.30.0.0/16",
|
||||
:driver=>"bridge",
|
||||
:id=>"30e017d5-488f-5a2f-a3ke-k8dce8246b60"}],
|
||||
[:private_network,
|
||||
{:type=>"dhcp",
|
||||
:ipv6=>"true",
|
||||
:subnet=>"2a02:6b8:b010:9020:1::/80",
|
||||
:protocol=>"tcp",
|
||||
:id=>"b8f23054-38d5-45c3-99ea-d33fc5d1b9f2"}],
|
||||
[:forwarded_port,
|
||||
{:guest=>22, :host=>2200, :host_ip=>"127.0.0.1", :id=>"ssh", :auto_correct=>true, :protocol=>"tcp"}]]
|
||||
}
|
||||
|
||||
subject { described_class.new(app, env) }
|
||||
|
||||
after do
|
||||
sandbox.close
|
||||
end
|
||||
|
||||
describe "#call" do
|
||||
it "calls the next action in the chain" do
|
||||
allow(driver).to receive(:host_vm?).and_return(false)
|
||||
allow(driver).to receive(:connect_network).and_return(true)
|
||||
|
||||
called = false
|
||||
app = ->(*args) { called = true }
|
||||
|
||||
action = described_class.new(app, env)
|
||||
|
||||
action.call(env)
|
||||
|
||||
expect(called).to eq(true)
|
||||
end
|
||||
|
||||
it "connects all of the avaiable networks to a container" do
|
||||
expect(driver).to receive(:connect_network).with("vagrant_network_172.20.0.0/16", "12345", ["--ip", "172.20.128.2", "--alias", "mynetwork"])
|
||||
expect(driver).to receive(:connect_network).with("vagrant_network_public_wlp4s0", "12345", ["--ip", "172.30.130.2"])
|
||||
expect(driver).to receive(:connect_network).with("vagrant_network_2a02:6b8:b010:9020:1::/80", "12345", [])
|
||||
|
||||
subject.call(env)
|
||||
end
|
||||
|
||||
context "with missing env values" do
|
||||
it "raises an error if the network name is missing" do
|
||||
env[:docker_connects] = {}
|
||||
|
||||
expect{subject.call(env)}.to raise_error(VagrantPlugins::DockerProvider::Errors::NetworkNameMissing)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#generate_connect_cli_arguments" do
|
||||
let(:network_options) {
|
||||
{:ip=>"172.20.128.2",
|
||||
:subnet=>"172.20.0.0/16",
|
||||
:driver=>"bridge",
|
||||
:internal=>"true",
|
||||
:alias=>"mynetwork",
|
||||
:protocol=>"tcp",
|
||||
:id=>"80e017d5-388f-4a2f-a3de-f8dce8156a58"} }
|
||||
|
||||
let(:false_network_options) {
|
||||
{:ip=>"172.20.128.2",
|
||||
:subnet=>"172.20.0.0/16",
|
||||
:driver=>"bridge",
|
||||
:internal=>"false",
|
||||
:alias=>"mynetwork",
|
||||
:protocol=>"tcp",
|
||||
:id=>"80e017d5-388f-4a2f-a3de-f8dce8156a58"} }
|
||||
|
||||
it "removes false values" do
|
||||
cli_args = subject.generate_connect_cli_arguments(false_network_options)
|
||||
expect(cli_args).to eq(["--ip", "172.20.128.2", "--subnet", "172.20.0.0/16", "--driver", "bridge", "--alias", "mynetwork", "--protocol", "tcp", "--id", "80e017d5-388f-4a2f-a3de-f8dce8156a58"])
|
||||
end
|
||||
|
||||
it "removes true and leaves flag value in arguments" do
|
||||
cli_args = subject.generate_connect_cli_arguments(network_options)
|
||||
expect(cli_args).to eq(["--ip", "172.20.128.2", "--subnet", "172.20.0.0/16", "--driver", "bridge", "--internal", "--alias", "mynetwork", "--protocol", "tcp", "--id", "80e017d5-388f-4a2f-a3de-f8dce8156a58"])
|
||||
end
|
||||
|
||||
it "takes options and generates cli flags" do
|
||||
cli_args = subject.generate_connect_cli_arguments(network_options)
|
||||
expect(cli_args).to eq(["--ip", "172.20.128.2", "--subnet", "172.20.0.0/16", "--driver", "bridge", "--internal", "--alias", "mynetwork", "--protocol", "tcp", "--id", "80e017d5-388f-4a2f-a3de-f8dce8156a58"])
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,104 @@
|
|||
require_relative "../../../../base"
|
||||
require_relative "../../../../../../plugins/providers/docker/action/destroy_network"
|
||||
|
||||
describe VagrantPlugins::DockerProvider::Action::DestroyNetwork do
|
||||
include_context "unit"
|
||||
|
||||
let(:sandbox) { isolated_environment }
|
||||
|
||||
let(:iso_env) do
|
||||
# We have to create a Vagrantfile so there is a root path
|
||||
sandbox.vagrantfile("")
|
||||
sandbox.create_vagrant_env
|
||||
end
|
||||
|
||||
let(:machine) do
|
||||
iso_env.machine(iso_env.machine_names[0], :docker).tap do |m|
|
||||
allow(m.provider).to receive(:driver).and_return(driver)
|
||||
allow(m.config.vm).to receive(:networks).and_return(networks)
|
||||
end
|
||||
end
|
||||
|
||||
let(:env) {{ machine: machine, ui: machine.ui, root_path: Pathname.new(".") }}
|
||||
let(:app) { lambda { |*args| }}
|
||||
let(:driver) { double("driver", create: "abcd1234") }
|
||||
|
||||
let(:networks) { [[:private_network,
|
||||
{:ip=>"172.20.128.2",
|
||||
:subnet=>"172.20.0.0/16",
|
||||
:driver=>"bridge",
|
||||
:internal=>"true",
|
||||
:alias=>"mynetwork",
|
||||
:protocol=>"tcp",
|
||||
:id=>"80e017d5-388f-4a2f-a3de-f8dce8156a58"}],
|
||||
[:private_network,
|
||||
{:type=>"dhcp",
|
||||
:ipv6=>"true",
|
||||
:subnet=>"2a02:6b8:b010:9020:1::/80",
|
||||
:protocol=>"tcp",
|
||||
:id=>"b8f23054-38d5-45c3-99ea-d33fc5d1b9f2"}],
|
||||
[:forwarded_port,
|
||||
{:guest=>22, :host=>2200, :host_ip=>"127.0.0.1", :id=>"ssh", :auto_correct=>true, :protocol=>"tcp"}]]
|
||||
}
|
||||
|
||||
subject { described_class.new(app, env) }
|
||||
|
||||
after do
|
||||
sandbox.close
|
||||
end
|
||||
|
||||
describe "#call" do
|
||||
let(:network_names) { ["vagrant_network_172.20.0.0/16", "vagrant_network_2a02:6b8:b010:9020:1::/80"] }
|
||||
|
||||
it "calls the next action in the chain" do
|
||||
allow(driver).to receive(:host_vm?).and_return(false)
|
||||
allow(driver).to receive(:existing_network?).and_return(true)
|
||||
allow(driver).to receive(:network_used?).and_return(true)
|
||||
allow(driver).to receive(:list_network_names).and_return([])
|
||||
|
||||
called = false
|
||||
app = ->(*args) { called = true }
|
||||
|
||||
action = described_class.new(app, env)
|
||||
action.call(env)
|
||||
|
||||
expect(called).to eq(true)
|
||||
end
|
||||
|
||||
it "calls the proper driver method to destroy the network" do
|
||||
allow(driver).to receive(:list_network_names).and_return(network_names)
|
||||
allow(driver).to receive(:host_vm?).and_return(false)
|
||||
allow(driver).to receive(:existing_named_network?).with("vagrant_network_172.20.0.0/16").
|
||||
and_return(true)
|
||||
allow(driver).to receive(:network_used?).with("vagrant_network_172.20.0.0/16").
|
||||
and_return(false)
|
||||
allow(driver).to receive(:existing_named_network?).with("vagrant_network_2a02:6b8:b010:9020:1::/80").
|
||||
and_return(true)
|
||||
allow(driver).to receive(:network_used?).with("vagrant_network_2a02:6b8:b010:9020:1::/80").
|
||||
and_return(false)
|
||||
|
||||
expect(driver).to receive(:rm_network).with("vagrant_network_172.20.0.0/16").twice
|
||||
expect(driver).to receive(:rm_network).with("vagrant_network_2a02:6b8:b010:9020:1::/80").twice
|
||||
|
||||
subject.call(env)
|
||||
end
|
||||
|
||||
it "doesn't destroy the network if another container is still using it" do
|
||||
allow(driver).to receive(:host_vm?).and_return(false)
|
||||
allow(driver).to receive(:list_network_names).and_return(network_names)
|
||||
allow(driver).to receive(:existing_named_network?).with("vagrant_network_172.20.0.0/16").
|
||||
and_return(true)
|
||||
allow(driver).to receive(:network_used?).with("vagrant_network_172.20.0.0/16").
|
||||
and_return(true)
|
||||
allow(driver).to receive(:existing_named_network?).with("vagrant_network_2a02:6b8:b010:9020:1::/80").
|
||||
and_return(true)
|
||||
allow(driver).to receive(:network_used?).with("vagrant_network_2a02:6b8:b010:9020:1::/80").
|
||||
and_return(true)
|
||||
|
||||
expect(driver).not_to receive(:rm_network).with("vagrant_network_172.20.0.0/16")
|
||||
expect(driver).not_to receive(:rm_network).with("vagrant_network_2a02:6b8:b010:9020:1::/80")
|
||||
|
||||
subject.call(env)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,340 @@
|
|||
require_relative "../../../../base"
|
||||
require_relative "../../../../../../plugins/providers/docker/action/prepare_networks"
|
||||
|
||||
describe VagrantPlugins::DockerProvider::Action::PrepareNetworks do
|
||||
include_context "unit"
|
||||
include_context "virtualbox"
|
||||
|
||||
let(:sandbox) { isolated_environment }
|
||||
|
||||
let(:iso_env) do
|
||||
# We have to create a Vagrantfile so there is a root path
|
||||
sandbox.vagrantfile("")
|
||||
sandbox.create_vagrant_env
|
||||
end
|
||||
|
||||
let(:machine) do
|
||||
iso_env.machine(iso_env.machine_names[0], :docker).tap do |m|
|
||||
allow(m.provider).to receive(:driver).and_return(driver)
|
||||
allow(m.config.vm).to receive(:networks).and_return(networks)
|
||||
end
|
||||
end
|
||||
|
||||
let(:env) {{ machine: machine, ui: machine.ui, root_path: Pathname.new(".") }}
|
||||
let(:app) { lambda { |*args| }}
|
||||
let(:driver) { double("driver", create: "abcd1234") }
|
||||
|
||||
let(:networks) { [[:private_network,
|
||||
{:ip=>"172.20.128.2",
|
||||
:subnet=>"172.20.0.0/16",
|
||||
:driver=>"bridge",
|
||||
:internal=>"true",
|
||||
:alias=>"mynetwork",
|
||||
:protocol=>"tcp",
|
||||
:id=>"80e017d5-388f-4a2f-a3de-f8dce8156a58"}],
|
||||
[:public_network,
|
||||
{:ip=>"172.30.130.2",
|
||||
:subnet=>"172.30.0.0/16",
|
||||
:driver=>"bridge",
|
||||
:id=>"30e017d5-488f-5a2f-a3ke-k8dce8246b60"}],
|
||||
[:private_network,
|
||||
{:type=>"dhcp",
|
||||
:ipv6=>"true",
|
||||
:subnet=>"2a02:6b8:b010:9020:1::/80",
|
||||
:protocol=>"tcp",
|
||||
:id=>"b8f23054-38d5-45c3-99ea-d33fc5d1b9f2"}],
|
||||
[:forwarded_port,
|
||||
{:guest=>22, :host=>2200, :host_ip=>"127.0.0.1", :id=>"ssh", :auto_correct=>true, :protocol=>"tcp"}]]
|
||||
}
|
||||
|
||||
let(:invalid_network) {
|
||||
[[:private_network,
|
||||
{:ipv6=>"true",
|
||||
:protocol=>"tcp",
|
||||
:id=>"b8f23054-38d5-45c3-99ea-d33fc5d1b9f2"}]]
|
||||
}
|
||||
|
||||
subject { described_class.new(app, env) }
|
||||
|
||||
after do
|
||||
sandbox.close
|
||||
end
|
||||
|
||||
describe "#call" do
|
||||
it "calls the next action in the chain" do
|
||||
allow(driver).to receive(:host_vm?).and_return(false)
|
||||
allow(driver).to receive(:existing_named_network?).and_return(false)
|
||||
allow(driver).to receive(:create_network).and_return(true)
|
||||
|
||||
called = false
|
||||
app = ->(*args) { called = true }
|
||||
|
||||
action = described_class.new(app, env)
|
||||
|
||||
allow(action).to receive(:process_public_network).and_return(["name", {}])
|
||||
allow(action).to receive(:process_private_network).and_return(["name", {}])
|
||||
|
||||
action.call(env)
|
||||
|
||||
expect(called).to eq(true)
|
||||
end
|
||||
|
||||
it "calls the proper driver methods to setup a network" do
|
||||
allow(driver).to receive(:host_vm?).and_return(false)
|
||||
allow(driver).to receive(:existing_named_network?).and_return(false)
|
||||
allow(driver).to receive(:network_containing_address).
|
||||
with("172.20.128.2").and_return(nil)
|
||||
allow(driver).to receive(:network_containing_address).
|
||||
with("192.168.1.1").and_return(nil)
|
||||
allow(driver).to receive(:network_defined?).with("172.20.128.0/24").
|
||||
and_return(false)
|
||||
allow(driver).to receive(:network_defined?).with("172.30.128.0/24").
|
||||
and_return(false)
|
||||
allow(driver).to receive(:network_defined?).with("2a02:6b8:b010:9020:1::/80").
|
||||
and_return(false)
|
||||
|
||||
allow(subject).to receive(:request_public_gateway).and_return("1234")
|
||||
allow(subject).to receive(:request_public_iprange).and_return("1234")
|
||||
|
||||
expect(subject).to receive(:process_private_network).with(networks[0][1], {}, env).
|
||||
and_return(["vagrant_network_172.20.128.0/24", {:ipv6=>false, :subnet=>"172.20.128.0/24"}])
|
||||
|
||||
expect(subject).to receive(:process_public_network).with(networks[1][1], {}, env).
|
||||
and_return(["vagrant_network_public_wlp4s0", {"opt"=>"parent=wlp4s0", "subnet"=>"192.168.1.0/24", "driver"=>"macvlan", "gateway"=>"1234", "ipv6"=>false, "ip_range"=>"1234"}])
|
||||
|
||||
expect(subject).to receive(:process_private_network).with(networks[2][1], {}, env).
|
||||
and_return(["vagrant_network_2a02:6b8:b010:9020:1::/80", {:ipv6=>true, :subnet=>"2a02:6b8:b010:9020:1::/80"}])
|
||||
|
||||
allow(machine.ui).to receive(:ask).and_return("1")
|
||||
|
||||
expect(driver).to receive(:create_network).
|
||||
with("vagrant_network_172.20.128.0/24", ["--subnet", "172.20.128.0/24"])
|
||||
expect(driver).to receive(:create_network).
|
||||
with("vagrant_network_public_wlp4s0", ["--opt", "parent=wlp4s0", "--subnet", "192.168.1.0/24", "--driver", "macvlan", "--gateway", "1234", "--ip-range", "1234"])
|
||||
expect(driver).to receive(:create_network).
|
||||
with("vagrant_network_2a02:6b8:b010:9020:1::/80", ["--ipv6", "--subnet", "2a02:6b8:b010:9020:1::/80"])
|
||||
|
||||
subject.call(env)
|
||||
|
||||
expect(env[:docker_connects]).to eq({0=>"vagrant_network_172.20.128.0/24", 1=>"vagrant_network_public_wlp4s0", 2=>"vagrant_network_2a02:6b8:b010:9020:1::/80"})
|
||||
end
|
||||
|
||||
it "uses an existing network if a matching subnet is found" do
|
||||
allow(driver).to receive(:host_vm?).and_return(false)
|
||||
allow(driver).to receive(:network_containing_address).
|
||||
with("172.20.128.2").and_return(nil)
|
||||
allow(driver).to receive(:network_containing_address).
|
||||
with("192.168.1.1").and_return(nil)
|
||||
allow(driver).to receive(:network_defined?).with("172.20.128.0/24").
|
||||
and_return("vagrant_network_172.20.128.0/24")
|
||||
allow(driver).to receive(:network_defined?).with("172.30.128.0/24").
|
||||
and_return("vagrant_network_public_wlp4s0")
|
||||
allow(driver).to receive(:network_defined?).with("2a02:6b8:b010:9020:1::/80").
|
||||
and_return("vagrant_network_2a02:6b8:b010:9020:1::/80")
|
||||
allow(machine.ui).to receive(:ask).and_return("1")
|
||||
|
||||
expect(driver).to receive(:existing_named_network?).
|
||||
with("vagrant_network_172.20.128.0/24").and_return(true)
|
||||
expect(driver).to receive(:existing_named_network?).
|
||||
with("vagrant_network_public_wlp4s0").and_return(true)
|
||||
expect(driver).to receive(:existing_named_network?).
|
||||
with("vagrant_network_2a02:6b8:b010:9020:1::/80").and_return(true)
|
||||
|
||||
expect(subject).to receive(:process_private_network).with(networks[0][1], {}, env).
|
||||
and_return(["vagrant_network_172.20.128.0/24", {:ipv6=>false, :subnet=>"172.20.128.0/24"}])
|
||||
|
||||
expect(subject).to receive(:process_public_network).with(networks[1][1], {}, env).
|
||||
and_return(["vagrant_network_public_wlp4s0", {"opt"=>"parent=wlp4s0", "subnet"=>"192.168.1.0/24", "driver"=>"macvlan", "gateway"=>"1234", "ipv6"=>false, "ip_range"=>"1234"}])
|
||||
|
||||
expect(subject).to receive(:process_private_network).with(networks[2][1], {}, env).
|
||||
and_return(["vagrant_network_2a02:6b8:b010:9020:1::/80", {:ipv6=>true, :subnet=>"2a02:6b8:b010:9020:1::/80"}])
|
||||
expect(driver).not_to receive(:create_network)
|
||||
|
||||
expect(subject).to receive(:validate_network_configuration!).
|
||||
with("vagrant_network_172.20.128.0/24", networks[0][1],
|
||||
{:ipv6=>false, :subnet=>"172.20.128.0/24"}, driver)
|
||||
|
||||
expect(subject).to receive(:validate_network_configuration!).
|
||||
with("vagrant_network_public_wlp4s0", networks[1][1],
|
||||
{"opt"=>"parent=wlp4s0", "subnet"=>"192.168.1.0/24", "driver"=>"macvlan", "gateway"=>"1234", "ipv6"=>false, "ip_range"=>"1234"}, driver)
|
||||
|
||||
expect(subject).to receive(:validate_network_configuration!).
|
||||
with("vagrant_network_2a02:6b8:b010:9020:1::/80", networks[2][1],
|
||||
{:ipv6=>true, :subnet=>"2a02:6b8:b010:9020:1::/80"}, driver)
|
||||
|
||||
subject.call(env)
|
||||
end
|
||||
|
||||
it "raises an error if an inproper network configuration is given" do
|
||||
allow(machine.config.vm).to receive(:networks).and_return(invalid_network)
|
||||
allow(driver).to receive(:host_vm?).and_return(false)
|
||||
allow(driver).to receive(:existing_network?).and_return(false)
|
||||
|
||||
expect{ subject.call(env) }.to raise_error(VagrantPlugins::DockerProvider::Errors::NetworkIPAddressRequired)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#generate_create_cli_arguments" do
|
||||
let(:network_options) {
|
||||
{:ip=>"172.20.128.2",
|
||||
:subnet=>"172.20.0.0/16",
|
||||
:driver=>"bridge",
|
||||
:internal=>"true",
|
||||
:alias=>"mynetwork",
|
||||
:protocol=>"tcp",
|
||||
:id=>"80e017d5-388f-4a2f-a3de-f8dce8156a58"} }
|
||||
|
||||
let(:false_network_options) {
|
||||
{:ip=>"172.20.128.2",
|
||||
:subnet=>"172.20.0.0/16",
|
||||
:driver=>"bridge",
|
||||
:internal=>"false",
|
||||
:alias=>"mynetwork",
|
||||
:protocol=>"tcp",
|
||||
:id=>"80e017d5-388f-4a2f-a3de-f8dce8156a58"} }
|
||||
|
||||
it "returns an array of cli arguments" do
|
||||
cli_args = subject.generate_create_cli_arguments(network_options)
|
||||
expect(cli_args).to eq( ["--ip", "172.20.128.2", "--subnet", "172.20.0.0/16", "--driver", "bridge", "--internal", "--alias", "mynetwork", "--protocol", "tcp", "--id", "80e017d5-388f-4a2f-a3de-f8dce8156a58"])
|
||||
end
|
||||
|
||||
it "removes option if set to false" do
|
||||
cli_args = subject.generate_create_cli_arguments(false_network_options)
|
||||
expect(cli_args).to eq( ["--ip", "172.20.128.2", "--subnet", "172.20.0.0/16", "--driver", "bridge", "--alias", "mynetwork", "--protocol", "tcp", "--id", "80e017d5-388f-4a2f-a3de-f8dce8156a58"])
|
||||
end
|
||||
end
|
||||
|
||||
describe "#validate_network_name!" do
|
||||
let(:netname) { "vagrant_network" }
|
||||
|
||||
it "returns true if name exists" do
|
||||
allow(driver).to receive(:existing_named_network?).with(netname).
|
||||
and_return(true)
|
||||
|
||||
expect(subject.validate_network_name!(netname, env)).to be_truthy
|
||||
end
|
||||
|
||||
it "raises an error if name does not exist" do
|
||||
allow(driver).to receive(:existing_named_network?).with(netname).
|
||||
and_return(false)
|
||||
|
||||
expect{subject.validate_network_name!(netname, env)}.to raise_error(VagrantPlugins::DockerProvider::Errors::NetworkNameUndefined)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#validate_network_configuration!" do
|
||||
let(:netname) { "vagrant_network_172.20.128.0/24" }
|
||||
let(:options) { {:ip=>"172.20.128.2", :subnet=>"172.20.0.0/16", :driver=>"bridge", :internal=>"true", :alias=>"mynetwork", :protocol=>"tcp", :id=>"80e017d5-388f-4a2f-a3de-f8dce8156a58", :netmask=>24} }
|
||||
let(:network_options) { {:ipv6=>false, :subnet=>"172.20.128.0/24"} }
|
||||
|
||||
it "returns true if all options are valid" do
|
||||
allow(driver).to receive(:network_containing_address).with(options[:ip]).
|
||||
and_return(netname)
|
||||
allow(driver).to receive(:network_containing_address).with(network_options[:subnet]).
|
||||
and_return(netname)
|
||||
|
||||
expect(subject.validate_network_configuration!(netname, options, network_options, driver)).
|
||||
to be_truthy
|
||||
end
|
||||
|
||||
it "raises an error of the address is invalid" do
|
||||
allow(driver).to receive(:network_containing_address).with(options[:ip]).
|
||||
and_return("fakename")
|
||||
expect{subject.validate_network_configuration!(netname, options, network_options, driver)}.
|
||||
to raise_error(VagrantPlugins::DockerProvider::Errors::NetworkAddressInvalid)
|
||||
end
|
||||
|
||||
it "raises an error of the subnet is invalid" do
|
||||
allow(driver).to receive(:network_containing_address).with(options[:ip]).
|
||||
and_return(netname)
|
||||
allow(driver).to receive(:network_containing_address).with(network_options[:subnet]).
|
||||
and_return("fakename")
|
||||
|
||||
expect{subject.validate_network_configuration!(netname, options, network_options, driver)}.
|
||||
to raise_error(VagrantPlugins::DockerProvider::Errors::NetworkSubnetInvalid)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#process_private_network" do
|
||||
let(:options) { {:ip=>"172.20.128.2", :subnet=>"172.20.0.0/16", :driver=>"bridge", :internal=>"true", :alias=>"mynetwork", :protocol=>"tcp", :id=>"80e017d5-388f-4a2f-a3de-f8dce8156a58", :netmask=>24} }
|
||||
let(:dhcp_options) { {type: "dhcp"} }
|
||||
let(:bad_options) { {driver: "bridge"} }
|
||||
|
||||
it "generates a network name and config for a dhcp private network" do
|
||||
network_name, network_options = subject.process_private_network(dhcp_options, {}, env)
|
||||
|
||||
expect(network_name).to eq("vagrant_network")
|
||||
expect(network_options).to eq({})
|
||||
end
|
||||
|
||||
it "generates a network name and options for a static ip" do
|
||||
allow(driver).to receive(:network_defined?).and_return(nil)
|
||||
network_name, network_options = subject.process_private_network(options, {}, env)
|
||||
expect(network_name).to eq("vagrant_network_172.20.0.0/16")
|
||||
expect(network_options).to eq({:ipv6=>false, :subnet=>"172.20.0.0/16"})
|
||||
end
|
||||
|
||||
it "raises an error if no ip address or type `dhcp` was given" do
|
||||
expect{subject.process_private_network(bad_options, {}, env)}.
|
||||
to raise_error(VagrantPlugins::DockerProvider::Errors::NetworkIPAddressRequired)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#process_public_network" do
|
||||
let(:options) { {:ip=>"172.30.130.2", :subnet=>"172.30.0.0/16", :driver=>"bridge", :id=>"30e017d5-488f-5a2f-a3ke-k8dce8246b60"} }
|
||||
let(:ipaddr) { double("ipaddr", prefix: 22, succ: "10.1.10.2", ipv6?: false) }
|
||||
|
||||
it "raises an error if there are no network interfaces" do
|
||||
expect(subject).to receive(:list_interfaces).and_return([])
|
||||
|
||||
expect{subject.process_public_network(options, {}, env)}.
|
||||
to raise_error(VagrantPlugins::DockerProvider::Errors::NetworkNoInterfaces)
|
||||
end
|
||||
|
||||
it "generates a network name and configuration" do
|
||||
allow(machine.ui).to receive(:ask).and_return("1")
|
||||
allow(subject).to receive(:request_public_gateway).and_return("1234")
|
||||
allow(subject).to receive(:request_public_iprange).and_return("1234")
|
||||
allow(IPAddr).to receive(:new).and_return(ipaddr)
|
||||
allow(driver).to receive(:existing_named_network?).and_return(false)
|
||||
allow(driver).to receive(:network_containing_address).
|
||||
with("10.1.10.2").and_return("vagrant_network_public")
|
||||
|
||||
network_name, network_options = subject.process_public_network(options, {}, env)
|
||||
expect(network_name).to eq("vagrant_network_public")
|
||||
end
|
||||
end
|
||||
|
||||
describe "#request_public_gateway" do
|
||||
let(:options) { {:ip=>"172.30.130.2", :subnet=>"172.30.0.0/16", :driver=>"bridge", :id=>"30e017d5-488f-5a2f-a3ke-k8dce8246b60"} }
|
||||
let(:ipaddr) { double("ipaddr", to_s: "172.30.130.2", prefix: 22, succ: "172.30.130.3",
|
||||
ipv6?: false) }
|
||||
|
||||
it "requests a gateway" do
|
||||
allow(IPAddr).to receive(:new).and_return(ipaddr)
|
||||
allow(ipaddr).to receive(:include?).and_return(false)
|
||||
allow(machine.ui).to receive(:ask).and_return("1")
|
||||
|
||||
addr = subject.request_public_gateway(options, "bridge", env)
|
||||
|
||||
expect(addr).to eq("172.30.130.2")
|
||||
end
|
||||
end
|
||||
|
||||
describe "#request_public_iprange" do
|
||||
let(:options) { {:ip=>"172.30.130.2", :subnet=>"172.30.0.0/16", :driver=>"bridge", :id=>"30e017d5-488f-5a2f-a3ke-k8dce8246b60"} }
|
||||
let(:ipaddr) { double("ipaddr", to_s: "172.30.100.2", prefix: 22, succ: "172.30.100.3",
|
||||
ipv6?: false) }
|
||||
let(:subnet) { double("ipaddr", to_s: "172.30.130.2", prefix: 22, succ: "172.30.130.3",
|
||||
ipv6?: false) }
|
||||
|
||||
it "requests a public ip range" do
|
||||
allow(IPAddr).to receive(:new).with(options[:subnet]).and_return(subnet)
|
||||
allow(IPAddr).to receive(:new).with("172.30.130.2").and_return(ipaddr)
|
||||
allow(subnet).to receive(:include?).and_return(true)
|
||||
allow(machine.ui).to receive(:ask).and_return(options[:ip])
|
||||
|
||||
addr = subject.request_public_iprange(options, "bridge", env)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -10,6 +10,149 @@ describe VagrantPlugins::DockerProvider::Driver 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 '#create' do
|
||||
let(:params) { {
|
||||
image: 'jimi/hendrix:electric-ladyland',
|
||||
|
@ -251,4 +394,139 @@ describe VagrantPlugins::DockerProvider::Driver do
|
|||
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
|
||||
|
|
|
@ -73,8 +73,6 @@ This helps keep your Vagrantfile similar to how it has always looked.
|
|||
The Docker provider does not support specifying options for `owner` or `group`
|
||||
on folders synced with a docker container.
|
||||
|
||||
Private and public networks are not currently supported.
|
||||
|
||||
### Volume Consistency
|
||||
|
||||
Docker's [volume consistency](https://docs.docker.com/v17.09/engine/admin/volumes/bind-mounts/) setting can be specified using the `docker_consistency` option when defining a synced folder. This can
|
||||
|
|
|
@ -0,0 +1,290 @@
|
|||
---
|
||||
layout: "docs"
|
||||
page_title: "Networking - Docker Provider"
|
||||
sidebar_current: "providers-docker-networking"
|
||||
description: |-
|
||||
The Vagrant Docker provider supports using the private network using the
|
||||
`docker network` commands.
|
||||
---
|
||||
|
||||
# Networking
|
||||
|
||||
Vagrant uses the `docker network` command under the hood to create and manage
|
||||
networks for containers. Vagrant will do its best to create and manage networks
|
||||
for any containers configured inside the Vagrantfile. Each docker network is grouped
|
||||
by the subnet used for a requested ip address.
|
||||
|
||||
For each newly unique network, Vagrant will run the `docker network create` subcommand
|
||||
with the provided options from the network config inside your Vagrantfile. If multiple
|
||||
networks share the same subnet, Vagrant will reuse that existing network for multiple
|
||||
containers. Once these networks have been created, Vagrant will attach these
|
||||
networks to the requested containers using the `docker network connect` for each
|
||||
network.
|
||||
|
||||
Vagrant names the networks inside docker as `vagrant_network` or `vagrant_network_<subnet here>`
|
||||
where `<subnet_here>` is the subnet for the network if defined by the user. An
|
||||
example of these networks is shown later in this page. If no subnet is requested
|
||||
for the network, Vagrant will connect the `vagrant_network` to the container.
|
||||
|
||||
When destroying containers through Vagrant, Vagrant will clean up the network if
|
||||
there are no more containers using the network.
|
||||
|
||||
## Docker Network Options
|
||||
|
||||
Most of the options work similar to other Vagrant providers. Defining either an
|
||||
ip or using `type: 'dhcp'` will give you a network on your container.
|
||||
|
||||
```ruby
|
||||
docker.vm.network :private_network, type: "dhcp"
|
||||
docker.vm.network :private_network, ip: "172.20.128.2"
|
||||
```
|
||||
|
||||
If you want to set something specific with a new network you can use scoped options
|
||||
which align with the command line flags for the [docker network create](https://docs.docker.com/engine/reference/commandline/network_create/)
|
||||
command. If there are any specific options you want to enable from the `docker network create`
|
||||
command, you can specify them like this:
|
||||
|
||||
```ruby
|
||||
docker.vm.network :private_network, type: "dhcp", docker_network__internal: true
|
||||
```
|
||||
|
||||
This will enable the `internal` option for the network when created with `docker network create`.
|
||||
|
||||
Where `option` corresponds to the given flag that will be provided to the `docker network create`
|
||||
command. Similarly, if there is a value you wish to enable when connecting a container
|
||||
to a given network, you can use the following value in your network config:
|
||||
|
||||
```ruby
|
||||
docker_connect__option: "value"
|
||||
```
|
||||
|
||||
When the docker provider creates a new network a netmask is required. If the netmask
|
||||
is not provided, Vagrant will default to a `/24` for IPv4 and `/64` for IPv6. To provide
|
||||
a different mask, set it using the `netmask` option:
|
||||
|
||||
```ruby
|
||||
docker.vm.network :private_network, ip: "172.20.128.2", netmask: 16
|
||||
```
|
||||
|
||||
For networks which set the type to "dhcp", it is also possible to specify a specific
|
||||
subnet for the network connection. This allows containers to connect to networks other
|
||||
than the default `vagrant_network` network. The docker provider supports specifying
|
||||
the desired subnet in two ways. The first is by using the `ip` and `netmask` options:
|
||||
|
||||
```ruby
|
||||
docker.vm.network :private_network, type: "dhcp", ip: "172.20.128.0", netmask: 24
|
||||
```
|
||||
|
||||
The second is by using the `subnet` option:
|
||||
|
||||
```ruby
|
||||
docker.vm.network :private_network, type: "dhcp", subnet: "172.20.128.0/24"
|
||||
```
|
||||
|
||||
### Public Networks
|
||||
|
||||
The Vagrant docker provider also supports defining public networks. The easiest way
|
||||
to define a public network is by setting the `type` option to "dhcp":
|
||||
|
||||
```ruby
|
||||
docker.vm.network :public_network, type: "dhcp"
|
||||
```
|
||||
|
||||
A bridge interface is required when setting up a public network. When no bridge
|
||||
device name is provided, Vagrant will prompt for the appropriate device to use. This
|
||||
can also be set using the `bridge` option:
|
||||
|
||||
```ruby
|
||||
docker.vm.network :public_network, type: "dhcp", bridge: "eth0"
|
||||
```
|
||||
|
||||
The `bridge` option also supports a list of interfaces which can be used for
|
||||
setting up the network. Vagrant will inspect the defined interfaces and use
|
||||
the first active interface when setting up the network:
|
||||
|
||||
```ruby
|
||||
docker.vm.network :public_network, type: "dhcp", bridge: ["eth0", "wlan0"]
|
||||
```
|
||||
|
||||
The available IP range for the bridge interface must be known when setting up
|
||||
the docker network. Even though a DHCP service may be available on the public
|
||||
network, docker will manage IP addresses provided to containers. This means
|
||||
that the subnet provided when defining the available IP range for the network
|
||||
should not be included within the subnet managed by the DHCP service. Vagrant
|
||||
will prompt for the available IP range information, however, it can also be
|
||||
provided in the Vagrantfile using the `docker_network__ip_range` option:
|
||||
|
||||
```ruby
|
||||
docker.vm.network :public_network, type: "dhcp", bridge: "eth0", docker_network__ip_range: "192.168.1.252/30"
|
||||
```
|
||||
|
||||
Finally, the gateway for the interface is required during setup. The docker
|
||||
provider will default the gateway address to the first address available for
|
||||
the subnet of the bridge device. Vagrant will prompt for confirmation to use
|
||||
the default address. The address can also be manually set in the Vagrantfile
|
||||
using the `docker_network__gateway` option:
|
||||
|
||||
```ruby
|
||||
docker.vm.network :public_network, type: "dhcp", bridge: "eth0", docker_network__gateway: "192.168.1.2"
|
||||
```
|
||||
|
||||
More examples are shared below which demonstrate creating a few common network
|
||||
interfaces.
|
||||
|
||||
## Docker Network Example
|
||||
|
||||
The following Vagrantfile will generate these networks for a container:
|
||||
|
||||
1. A IPv4 IP address assigned by DHCP
|
||||
2. A IPv4 IP address 172.20.128.2 on a network with subnet 172.20.0.0/16
|
||||
3. A IPv6 IP address assigned by DHCP on subnet 2a02:6b8:b010:9020:1::/80
|
||||
|
||||
```ruby
|
||||
Vagrant.configure("2") do |config|
|
||||
config.vm.define "docker" do |docker|
|
||||
docker.vm.network :private_network, type: "dhcp", docker_network__internal: true
|
||||
docker.vm.network :private_network,
|
||||
ip: "172.20.128.2", netmask: "16"
|
||||
docker.vm.network :private_network, type: "dhcp", subnet: "2a02:6b8:b010:9020:1::/80"
|
||||
docker.vm.provider "docker" do |d|
|
||||
d.build_dir = "docker_build_dir"
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
You can test that your container has the proper configured networks by looking
|
||||
at the result of running `ip addr`, for example:
|
||||
|
||||
```
|
||||
brian@localghost:vagrant-sandbox % docker ps ±[●][master]
|
||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
||||
370f4e5d2217 196a06ef12f5 "tail -f /dev/null" 5 seconds ago Up 3 seconds 80/tcp, 443/tcp vagrant-sandbox_docker-1_1551810440
|
||||
brian@localghost:vagrant-sandbox % docker exec 370f4e5d2217 ip addr ±[●][master]
|
||||
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
|
||||
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
|
||||
inet 127.0.0.1/8 scope host lo
|
||||
valid_lft forever preferred_lft forever
|
||||
inet6 ::1/128 scope host
|
||||
valid_lft forever preferred_lft forever
|
||||
24: eth0@if25: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
|
||||
link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0
|
||||
inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0
|
||||
valid_lft forever preferred_lft forever
|
||||
27: eth1@if28: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
|
||||
link/ether 02:42:ac:13:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
|
||||
inet 172.19.0.2/16 brd 172.19.255.255 scope global eth1
|
||||
valid_lft forever preferred_lft forever
|
||||
30: eth2@if31: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
|
||||
link/ether 02:42:ac:14:80:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
|
||||
inet 172.20.128.2/16 brd 172.20.255.255 scope global eth2
|
||||
valid_lft forever preferred_lft forever
|
||||
33: eth3@if34: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
|
||||
link/ether 02:42:ac:15:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
|
||||
inet 172.21.0.2/16 brd 172.21.255.255 scope global eth3
|
||||
valid_lft forever preferred_lft forever
|
||||
inet6 2a02:6b8:b010:9020:1::2/80 scope global nodad
|
||||
valid_lft forever preferred_lft forever
|
||||
inet6 fe80::42:acff:fe15:2/64 scope link
|
||||
valid_lft forever preferred_lft forever
|
||||
```
|
||||
|
||||
You can also connect your containers to a docker network that was created outside
|
||||
of Vagrant:
|
||||
|
||||
```
|
||||
$ docker network create my-custom-network --subnet=172.20.0.0/16
|
||||
```
|
||||
|
||||
```ruby
|
||||
Vagrant.configure("2") do |config|
|
||||
config.vm.define "docker" do |docker|
|
||||
docker.vm.network :private_network, type: "dhcp" name: "my-custom-network"
|
||||
docker.vm.provider "docker" do |d|
|
||||
d.build_dir = "docker_build_dir"
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
Vagrant will not delete or modify these outside networks when deleting the container, however.
|
||||
|
||||
## Useful Debugging Tips
|
||||
|
||||
The `docker network` command provides some helpful insights to what might be going
|
||||
on with the networks Vagrant creates. For example, if you want to know what networks
|
||||
you currently have running on your machine, you can run the `docker network ls` command:
|
||||
|
||||
```
|
||||
brian@localghost:vagrant-sandbox % docker network ls ±[●][master]
|
||||
NETWORK ID NAME DRIVER SCOPE
|
||||
a2bfc26bd876 bridge bridge local
|
||||
2a2845e77550 host host local
|
||||
f36682aeba68 none null local
|
||||
00d4986c7dc2 vagrant_network bridge local
|
||||
d02420ff4c39 vagrant_network_2a02:6b8:b010:9020:1::/80 bridge local
|
||||
799ae9dbaf98 vagrant_network_172.20.0.0/16 bridge local
|
||||
```
|
||||
|
||||
You can also inspect any network for more information:
|
||||
|
||||
```
|
||||
brian@localghost:vagrant-sandbox % docker network inspect vagrant_network ±[●][master]
|
||||
[
|
||||
{
|
||||
"Name": "vagrant_network",
|
||||
"Id": "00d4986c7dc2ed7bf1961989ae1cfe98504c711f9de2f547e5dfffe2bb819fc2",
|
||||
"Created": "2019-03-05T10:27:21.558824922-08:00",
|
||||
"Scope": "local",
|
||||
"Driver": "bridge",
|
||||
"EnableIPv6": false,
|
||||
"IPAM": {
|
||||
"Driver": "default",
|
||||
"Options": {},
|
||||
"Config": [
|
||||
{
|
||||
"Subnet": "172.19.0.0/16",
|
||||
"Gateway": "172.19.0.1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Internal": false,
|
||||
"Attachable": false,
|
||||
"Ingress": false,
|
||||
"ConfigFrom": {
|
||||
"Network": ""
|
||||
},
|
||||
"ConfigOnly": false,
|
||||
"Containers": {
|
||||
"370f4e5d2217e698b16376583fbf051dd34018e5fd18958b604017def92fea63": {
|
||||
"Name": "vagrant-sandbox_docker-1_1551810440",
|
||||
"EndpointID": "166b7ca8960a9f20a150bb75a68d07e27e674781ed9f916e9aa58c8bc2539a61",
|
||||
"MacAddress": "02:42:ac:13:00:02",
|
||||
"IPv4Address": "172.19.0.2/16",
|
||||
"IPv6Address": ""
|
||||
}
|
||||
},
|
||||
"Options": {},
|
||||
"Labels": {}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Caveats
|
||||
|
||||
For now, Vagrant only looks at the subnet when figuring out if it should create
|
||||
a new network for a guest container. If you bring up a container with a network,
|
||||
and then change or add some new options (but leave the subnet the same), it will
|
||||
not apply those changes or create a new network.
|
||||
|
||||
Because the `--link` flag for the `docker network connect` command is considered
|
||||
legacy, Vagrant does not support that option when creating containers and connecting
|
||||
networks.
|
||||
|
||||
## More Information
|
||||
|
||||
For more information on how docker manages its networks, please refer to their
|
||||
documentation:
|
||||
|
||||
- https://docs.docker.com/network/
|
||||
- https://docs.docker.com/engine/reference/commandline/network/
|
|
@ -166,6 +166,7 @@
|
|||
<li<%= sidebar_current("providers-docker-commands") %>><a href="/docs/docker/commands.html">Commands</a></li>
|
||||
<li<%= sidebar_current("providers-docker-boxes") %>><a href="/docs/docker/boxes.html">Boxes</a></li>
|
||||
<li<%= sidebar_current("providers-docker-configuration") %>><a href="/docs/docker/configuration.html">Configuration</a></li>
|
||||
<li<%= sidebar_current("providers-docker-networking") %>><a href="/docs/docker/networking.html">Networking</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li<%= sidebar_current("providers-hyperv") %>>
|
||||
|
|
Loading…
Reference in New Issue