Docker provider networking support updates

Use `mask` option for defining subnet on network configuration. Allow
options to be passed through using hash scoping and docker_network and
docker_connect prefixes. Enable public networks. Allow configuration
to define pre-existing networks by name.
This commit is contained in:
Chris Roberts 2019-03-19 11:29:16 -07:00
parent 1224622387
commit a645ce3c25
9 changed files with 555 additions and 159 deletions

View File

@ -244,6 +244,7 @@ module VagrantPlugins
b2.use PrepareNFSValidIds
b2.use SyncedFolderCleanup
b2.use PrepareNFSSettings
b2.use PrepareNetworks
b2.use Login
b2.use Build
@ -266,7 +267,7 @@ module VagrantPlugins
end
end
b2.use Network
b2.use ConnectNetworks
b2.use Start
b2.use WaitForRunning
@ -294,6 +295,7 @@ 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")
@ -311,13 +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 :Network, action_root.join("network")
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

View File

@ -0,0 +1,75 @@
require 'ipaddr'
require 'log4r'
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 == false
# If value is true, consider feature flag with no value
opt = value == 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]
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

View File

@ -18,22 +18,22 @@ module VagrantPlugins
end
machine.config.vm.networks.each do |type, options|
# We only handle private networks
next if type != :private_network
next if type != :private_network && type != :public_network
if options[:subnet]
network_name = "vagrant_network_#{options[:subnet]}"
else
network_name = "vagrant_network"
end
machine.env.lock("docker-network-destroy", retry: true) do
vagrant_networks = machine.provider.driver.list_network_names.find_all do |n|
n.start_with?("vagrant_network")
end
# Only cleans up networks defined by Vagrant
if machine.provider.driver.existing_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")
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

View File

@ -1,102 +0,0 @@
require 'log4r'
module VagrantPlugins
module DockerProvider
module Action
class Network
def initialize(app, env)
@app = app
@logger = Log4r::Logger.new('vagrant::plugins::docker::network')
end
# @param[Hash] options - options from the network config
# @returns[Array] cli_opts - an array of strings used for the network commnad
def generate_create_cli_arguments(options)
cli_opts = []
ignored_options = ["ip", "protocol", "id", "alias"].map(&:freeze).freeze
# Splits the networking options to generate the proper CLI flags for docker
options.each do |opt, value|
opt = opt.to_s
if (opt == "type" && value == "dhcp") || ignored_options.include?(opt)
# `docker network create` doesn't care about these options
next
else
cli_opts.concat(["--#{opt}=#{value.to_s}"])
end
end
return cli_opts
end
# @param[Hash] options - options from the network config
# @returns[Array] cli_opts - an array of strings used for the network commnad
def generate_connect_cli_arguments(options)
cli_opts = []
if options[:ip]
cli_opts = ["--ip", options[:ip]]
elsif options[:ip6]
cli_opts = ["--ip6", options[:ip6]]
end
if options[:alias]
cli_opts.concat(["--alias=#{options[:alias]}"])
end
return cli_opts
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
env[:ui].info(I18n.t("docker_provider.network_configure"))
machine.config.vm.networks.each do |type, options|
# We only handle private networks
next if type != :private_network
cli_opts = generate_create_cli_arguments(options)
if options[:subnet]
existing_network = machine.provider.driver.subnet_defined?(options[:subnet])
if !existing_network
network_name = "vagrant_network_#{options[:subnet]}"
else
env[:ui].warn(I18n.t("docker_provider.subnet_exists",
network_name: existing_network,
subnet: options[:subnet]))
network_name = existing_network
end
elsif options[:type] == "dhcp"
network_name = "vagrant_network"
else
raise Errors::NetworkInvalidOption, container: machine.name
end
container_id = machine.id
machine.env.lock("docker-network-create", retry: true) do
if !machine.provider.driver.existing_network?(network_name)
@logger.debug("Creating network #{network_name}")
machine.provider.driver.create_network(network_name, cli_opts)
else
@logger.debug("Network #{network_name} already created")
end
end
@logger.debug("Connecting network #{network_name} to container guest #{machine.name}")
connect_opts = generate_connect_cli_arguments(options)
machine.provider.driver.connect_network(network_name, container_id, connect_opts)
end
@app.call(env)
end
end
end
end
end

View File

@ -0,0 +1,335 @@
require 'ipaddr'
require 'log4r'
module VagrantPlugins
module DockerProvider
module Action
class PrepareNetworks
include Vagrant::Util::ScopedHashOverride
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 == false
# If value is true, consider feature flag with no value
opt = value == 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] name Name of existing network
# @return [Boolean]
def validate_network_name!(name)
if !env[:machine].provider.driver.existing_named_network?(network_name)
raise Errors::NetworkNameUndefined,
network_name: 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])
network_name = root_options[:name]
end
if root_options[:type].to_s == "dhcp"
network_name = "vagrant_network" if !network_name
return [network_name, network_options]
end
if !root_options[:ip]
raise Errors::NetworkIPAddressRequired
end
# Validate the IP address
addr = IPAddr.new(root_options[:ip])
# 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[:mask] && !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[:mask] = addr.ipv4? ? 24 : 64
end
end
# With no network name, process options to find or determine
# name for new network
if !network_name
subnet = IPAddr.new("#{root_options[:ip]}/#{root_options[:mask]}")
network = "#{subnet}/#{root_options[:mask]}"
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])
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)
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 = {}
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
env[:docker_connects] = connections
@app.call(env)
end
end
end
end
end

View File

@ -157,14 +157,20 @@ module VagrantPlugins
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)
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+/
@ -206,12 +212,18 @@ module VagrantPlugins
command = ['docker', 'network', 'inspect'] + Array(network)
command = command.push(*opts)
output = execute(*command)
output
begin
JSON.load(output)
rescue JSON::ParseError
@logger.warn("Failed to parse network inspection of network: #{network}")
@logger.debug("Failed network output content: `#{output.inspect}`")
nil
end
end
# @param [Array] opts - An array of flags used for listing networks
def list_network(opts=nil)
command = ['docker', 'network', 'ls'].push(*opts)
# @param [String] opts - Flags used for listing networks
def list_network(*opts)
command = ['docker', 'network', 'ls', *opts]
output = execute(*command)
output
end
@ -226,12 +238,11 @@ module VagrantPlugins
output
end
# TODO: Note...cli can optionally take a list of networks to delete.
# We might need this later, but for now our helper takes 1 network at a time
# Delete network(s)
#
# @param [String] network - name of network to remove
def rm_network(network)
command = ['docker', 'network', 'rm', network]
def rm_network(*network)
command = ['docker', 'network', 'rm', *network]
output = execute(*command)
output
end
@ -246,34 +257,55 @@ module VagrantPlugins
# ######################
# @param [String] subnet_string - Subnet to look for
def subnet_defined?(subnet_string)
all_networks = list_network(["--format={{.Name}}"])
all_networks = all_networks.split("\n")
def network_defined?(subnet_string)
all_networks = list_network_names
results = inspect_network(all_networks)
begin
networks_info = JSON.parse(results)
networks_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
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
rescue JSON::ParserError => e
@logger.warn("Could not properly parse response from `docker network inspect #{all_networks.join(" ")}`")
end
return nil
end
# Looks to see if a docker network has already been defined
# Locate network which contains given address
#
# @param [String] network - name of network to look for
def existing_network?(network)
result = list_network(["--format={{.Name}}"])
#TODO: we should be more explicit here if we can
result.match?(/#{network}/)
# @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
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.
@ -283,13 +315,8 @@ module VagrantPlugins
# @return [Bool,nil]
def network_used?(network)
result = inspect_network(network)
begin
result = JSON.parse(result)
return result.first["Containers"].size > 0
rescue JSON::ParserError => e
@logger.warn("Could not properly parse response from `docker network inspect #{network}`")
return nil
end
return nil if !result
return result.first["Containers"].size > 0
end
end

View File

@ -49,6 +49,14 @@ module VagrantPlugins
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 PackageNotSupported < DockerError
error_key(:package_not_supported)
end

View File

@ -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.

View File

@ -45,8 +45,33 @@ 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_configure: |-
Configuring and enabling network interfaces...
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: |-
@ -204,9 +229,33 @@ 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_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_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