vagrant-docker-networks-manager 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +16 -0
- data/LICENSE.md +22 -0
- data/README.md +320 -0
- data/lib/vagrant-docker-networks-manager/VERSION +1 -0
- data/lib/vagrant-docker-networks-manager/action.rb +204 -0
- data/lib/vagrant-docker-networks-manager/command.rb +553 -0
- data/lib/vagrant-docker-networks-manager/config.rb +164 -0
- data/lib/vagrant-docker-networks-manager/helpers.rb +98 -0
- data/lib/vagrant-docker-networks-manager/network_builder.rb +44 -0
- data/lib/vagrant-docker-networks-manager/plugin.rb +33 -0
- data/lib/vagrant-docker-networks-manager/util.rb +152 -0
- data/lib/vagrant-docker-networks-manager/version.rb +10 -0
- data/lib/vagrant-docker-networks-manager.rb +4 -0
- data/locales/en.yml +142 -0
- data/locales/fr.yml +142 -0
- metadata +108 -0
@@ -0,0 +1,164 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "ipaddr"
|
4
|
+
require_relative "helpers"
|
5
|
+
|
6
|
+
module VagrantDockerNetworksManager
|
7
|
+
class Config < Vagrant.plugin("2", :config)
|
8
|
+
attr_accessor :network_name, :network_subnet, :network_type, :network_gateway,
|
9
|
+
:network_parent, :network_attachable, :enable_ipv6, :ip_range,
|
10
|
+
:cleanup_on_destroy, :locale
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@network_name = UNSET_VALUE
|
14
|
+
@network_subnet = UNSET_VALUE
|
15
|
+
@network_type = UNSET_VALUE
|
16
|
+
@network_gateway = UNSET_VALUE
|
17
|
+
@network_parent = UNSET_VALUE
|
18
|
+
@network_attachable = UNSET_VALUE
|
19
|
+
@enable_ipv6 = UNSET_VALUE
|
20
|
+
@ip_range = UNSET_VALUE
|
21
|
+
@cleanup_on_destroy = UNSET_VALUE
|
22
|
+
@locale = UNSET_VALUE
|
23
|
+
end
|
24
|
+
|
25
|
+
def finalize!
|
26
|
+
@network_name = "network_lo1" if @network_name == UNSET_VALUE
|
27
|
+
@network_subnet = "172.28.100.0/26" if @network_subnet == UNSET_VALUE
|
28
|
+
@network_type = "bridge" if @network_type == UNSET_VALUE
|
29
|
+
@network_gateway = "172.28.100.1" if @network_gateway == UNSET_VALUE
|
30
|
+
@network_parent = nil if @network_parent == UNSET_VALUE
|
31
|
+
@network_attachable = false if @network_attachable == UNSET_VALUE
|
32
|
+
@enable_ipv6 = false if @enable_ipv6 == UNSET_VALUE
|
33
|
+
@ip_range = nil if @ip_range == UNSET_VALUE
|
34
|
+
@cleanup_on_destroy = true if @cleanup_on_destroy == UNSET_VALUE
|
35
|
+
@locale = "en" if @locale == UNSET_VALUE
|
36
|
+
end
|
37
|
+
|
38
|
+
def validate(_machine)
|
39
|
+
VagrantDockerNetworksManager::UiHelpers.setup_i18n! rescue nil
|
40
|
+
errors = []
|
41
|
+
|
42
|
+
unless @network_name.is_a?(String) && !@network_name.strip.empty? && docker_name?(@network_name)
|
43
|
+
errors << ::I18n.t("errors.invalid_name")
|
44
|
+
end
|
45
|
+
|
46
|
+
unless ipv4_cidr_aligned?(@network_subnet)
|
47
|
+
errors << ::I18n.t("errors.invalid_subnet")
|
48
|
+
end
|
49
|
+
|
50
|
+
unless @network_type.is_a?(String) && %w[bridge macvlan].include?(@network_type)
|
51
|
+
errors << ::I18n.t("errors.invalid_type")
|
52
|
+
end
|
53
|
+
|
54
|
+
if present?(@network_gateway)
|
55
|
+
if !ipv4?(@network_gateway)
|
56
|
+
errors << ::I18n.t("errors.invalid_gateway")
|
57
|
+
elsif ipv4_cidr_aligned?(@network_subnet) && !gateway_host_addr?(@network_subnet, @network_gateway)
|
58
|
+
errors << ::I18n.t("errors.invalid_gateway")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
if present?(@network_parent) && !@network_parent.is_a?(String)
|
63
|
+
errors << ::I18n.t("errors.invalid_parent")
|
64
|
+
end
|
65
|
+
|
66
|
+
if @network_type.to_s == "macvlan" && !present?(@network_parent)
|
67
|
+
errors << ::I18n.t("errors.invalid_parent")
|
68
|
+
end
|
69
|
+
|
70
|
+
unless [true, false].include?(@network_attachable)
|
71
|
+
errors << ::I18n.t("errors.invalid_attachable")
|
72
|
+
end
|
73
|
+
|
74
|
+
unless [true, false].include?(@enable_ipv6)
|
75
|
+
errors << ::I18n.t("errors.invalid_ipv6")
|
76
|
+
end
|
77
|
+
|
78
|
+
if present?(@ip_range)
|
79
|
+
if !ipv4_cidr?(@ip_range)
|
80
|
+
errors << ::I18n.t("errors.invalid_ip_range")
|
81
|
+
elsif ipv4_cidr_aligned?(@network_subnet) && !cidr_within_cidr?(@network_subnet, @ip_range)
|
82
|
+
errors << ::I18n.t("errors.invalid_ip_range")
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
unless [true, false].include?(@cleanup_on_destroy)
|
87
|
+
errors << ::I18n.t("errors.invalid_cleanup")
|
88
|
+
end
|
89
|
+
|
90
|
+
unless @locale.is_a?(String) && %w[fr en].include?(@locale.to_s[0,2].downcase)
|
91
|
+
errors << ::I18n.t("errors.invalid_locale")
|
92
|
+
end
|
93
|
+
|
94
|
+
{ "vagrant-docker-networks-manager" => errors }
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
def present?(val)
|
100
|
+
!val.nil? && !(val.respond_to?(:empty?) && val.empty?)
|
101
|
+
end
|
102
|
+
|
103
|
+
def ipv4?(str)
|
104
|
+
ip = IPAddr.new(str) rescue nil
|
105
|
+
ip&.ipv4? ? true : false
|
106
|
+
end
|
107
|
+
|
108
|
+
def ipv4_cidr?(str)
|
109
|
+
ip_str, mask_str = str.to_s.split("/", 2)
|
110
|
+
return false unless ip_str && mask_str&.match?(/^\d+$/)
|
111
|
+
m = mask_str.to_i
|
112
|
+
return false unless (0..32).include?(m)
|
113
|
+
ip = IPAddr.new(ip_str) rescue nil
|
114
|
+
ip&.ipv4? ? true : false
|
115
|
+
rescue
|
116
|
+
false
|
117
|
+
end
|
118
|
+
|
119
|
+
def ipv4_cidr_aligned?(str)
|
120
|
+
ip_str, mask_str = str.to_s.split("/", 2)
|
121
|
+
return false unless ip_str && mask_str&.match?(/^\d+$/)
|
122
|
+
m = mask_str.to_i
|
123
|
+
return false unless (0..32).include?(m)
|
124
|
+
ip = IPAddr.new(ip_str) rescue nil
|
125
|
+
return false unless ip&.ipv4?
|
126
|
+
ip.mask(m).to_s == ip_str
|
127
|
+
rescue
|
128
|
+
false
|
129
|
+
end
|
130
|
+
|
131
|
+
def gateway_host_addr?(cidr, gw)
|
132
|
+
net = IPAddr.new(cidr) rescue nil
|
133
|
+
ip = IPAddr.new(gw) rescue nil
|
134
|
+
return false unless net&.ipv4? && ip&.ipv4? && net.include?(ip)
|
135
|
+
mask = cidr.split("/")[1].to_i
|
136
|
+
network = IPAddr.new(net.to_range.first.to_s).mask(mask)
|
137
|
+
broadcast = IPAddr.new(net.to_range.last.to_s)
|
138
|
+
ip != network && ip != broadcast
|
139
|
+
rescue
|
140
|
+
false
|
141
|
+
end
|
142
|
+
|
143
|
+
def cidr_within_cidr?(outer, inner)
|
144
|
+
outer_ip, outer_mask = outer.to_s.split("/", 2)
|
145
|
+
inner_ip, inner_mask = inner.to_s.split("/", 2)
|
146
|
+
return false unless outer_ip && inner_ip && outer_mask&.match?(/^\d+$/) && inner_mask&.match?(/^\d+$/)
|
147
|
+
|
148
|
+
om = outer_mask.to_i
|
149
|
+
im = inner_mask.to_i
|
150
|
+
return false unless (0..32).include?(om) && (0..32).include?(im)
|
151
|
+
return false unless om <= im
|
152
|
+
|
153
|
+
outer_net = IPAddr.new(outer_ip).mask(om) rescue nil
|
154
|
+
inner_as_outer = IPAddr.new(inner_ip).mask(om) rescue nil
|
155
|
+
outer_net&.ipv4? && inner_as_outer&.ipv4? && (inner_as_outer.to_s == outer_net.to_s)
|
156
|
+
rescue
|
157
|
+
false
|
158
|
+
end
|
159
|
+
|
160
|
+
def docker_name?(s)
|
161
|
+
s.is_a?(String) && s.match?(/\A[a-zA-Z0-9][a-zA-Z0-9_.-]{0,126}\z/)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "yaml"
|
4
|
+
require "i18n"
|
5
|
+
|
6
|
+
module VagrantDockerNetworksManager
|
7
|
+
module UiHelpers
|
8
|
+
class MissingTranslationError < StandardError; end
|
9
|
+
class UnsupportedLocaleError < StandardError; end
|
10
|
+
|
11
|
+
SUPPORTED = [:en, :fr].freeze
|
12
|
+
OUR_NAMESPACES = %w[messages. errors. usage. help. prompts. log. emoji.].freeze
|
13
|
+
|
14
|
+
EMOJI = {
|
15
|
+
success: "✅",
|
16
|
+
info: "🔍",
|
17
|
+
ongoing: "🔁",
|
18
|
+
warning: "⚠️",
|
19
|
+
error: "❌",
|
20
|
+
version: "💾",
|
21
|
+
broom: "🧹",
|
22
|
+
question: "❓"
|
23
|
+
}.freeze
|
24
|
+
|
25
|
+
module_function
|
26
|
+
|
27
|
+
def setup_i18n!
|
28
|
+
return if defined?(@i18n_setup) && @i18n_setup
|
29
|
+
|
30
|
+
::I18n.enforce_available_locales = false
|
31
|
+
|
32
|
+
base = File.expand_path("../../locales", __dir__)
|
33
|
+
paths = Dir[File.join(base, "*.yml")]
|
34
|
+
::I18n.load_path |= paths
|
35
|
+
::I18n.available_locales = SUPPORTED
|
36
|
+
|
37
|
+
default = ((ENV["VDNM_LANG"] || ENV["LANG"] || "en")[0, 2] rescue "en").to_sym
|
38
|
+
::I18n.default_locale = SUPPORTED.include?(default) ? default : :en
|
39
|
+
|
40
|
+
::I18n.backend.load_translations
|
41
|
+
@i18n_setup = true
|
42
|
+
end
|
43
|
+
|
44
|
+
def set_locale!(lang)
|
45
|
+
setup_i18n!
|
46
|
+
sym = lang.to_s[0, 2].downcase.to_sym
|
47
|
+
unless SUPPORTED.include?(sym)
|
48
|
+
raise UnsupportedLocaleError,
|
49
|
+
"#{EMOJI[:error]} Unsupported language: #{sym}. Available: #{SUPPORTED.join(", ")}"
|
50
|
+
end
|
51
|
+
::I18n.locale = sym
|
52
|
+
::I18n.backend.load_translations
|
53
|
+
end
|
54
|
+
|
55
|
+
def e(key, no_emoji: false)
|
56
|
+
return "" if no_emoji
|
57
|
+
EMOJI[key] || ""
|
58
|
+
end
|
59
|
+
|
60
|
+
def t(key, **opts)
|
61
|
+
setup_i18n!
|
62
|
+
::I18n.t(key, **opts)
|
63
|
+
end
|
64
|
+
|
65
|
+
def t!(key, **opts)
|
66
|
+
setup_i18n!
|
67
|
+
k = key.to_s
|
68
|
+
if our_key?(k) && !::I18n.exists?(k, ::I18n.locale)
|
69
|
+
raise MissingTranslationError, "#{EMOJI[:error]} [#{::I18n.locale}] Missing translation for key: #{k}"
|
70
|
+
end
|
71
|
+
::I18n.t(k, **opts)
|
72
|
+
end
|
73
|
+
|
74
|
+
def t_hash(key)
|
75
|
+
setup_i18n!
|
76
|
+
v = ::I18n.t(key, default: {})
|
77
|
+
v.is_a?(Hash) ? v : {}
|
78
|
+
end
|
79
|
+
|
80
|
+
def print_general_help
|
81
|
+
setup_i18n!
|
82
|
+
puts t("help.general_title")
|
83
|
+
t_hash("help.commands").each_value { |line| puts " #{line}" }
|
84
|
+
end
|
85
|
+
|
86
|
+
def print_topic_help(topic)
|
87
|
+
setup_i18n!
|
88
|
+
topic = topic.to_s.downcase.strip
|
89
|
+
return print_general_help if topic.empty?
|
90
|
+
body = t("help.topic.#{topic}", default: nil)
|
91
|
+
body ? puts(body) : print_general_help
|
92
|
+
end
|
93
|
+
|
94
|
+
def our_key?(k)
|
95
|
+
OUR_NAMESPACES.any? { |ns| k.start_with?(ns) }
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "shellwords"
|
4
|
+
|
5
|
+
module VagrantDockerNetworksManager
|
6
|
+
class NetworkBuilder
|
7
|
+
def initialize(config, machine_id: nil)
|
8
|
+
@config = config
|
9
|
+
@machine_id = machine_id
|
10
|
+
end
|
11
|
+
|
12
|
+
def build_create_command_args
|
13
|
+
args = ["network", "create"]
|
14
|
+
|
15
|
+
args += ["--label", "com.vagrant.plugin=docker_networks_manager"]
|
16
|
+
args += ["--label", "com.vagrant.machine_id=#{@machine_id}"] if @machine_id
|
17
|
+
args += ["--driver", @config.network_type] if present?(@config.network_type)
|
18
|
+
args += ["--subnet", @config.network_subnet] if present?(@config.network_subnet)
|
19
|
+
args += ["--gateway", @config.network_gateway] if present?(@config.network_gateway)
|
20
|
+
args += ["--ip-range", @config.ip_range] if present?(@config.ip_range)
|
21
|
+
args << "--ipv6" if truthy?(@config.enable_ipv6)
|
22
|
+
args << "--attachable" if truthy?(@config.network_attachable)
|
23
|
+
if @config.network_type.to_s == "macvlan" && present?(@config.network_parent)
|
24
|
+
args += ["--opt", "parent=#{@config.network_parent}"]
|
25
|
+
end
|
26
|
+
args << @config.network_name.to_s
|
27
|
+
args
|
28
|
+
end
|
29
|
+
|
30
|
+
def build_create_command
|
31
|
+
build_create_command_args.shelljoin
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def present?(val)
|
37
|
+
!val.nil? && !(val.respond_to?(:empty?) && val.empty?)
|
38
|
+
end
|
39
|
+
|
40
|
+
def truthy?(val)
|
41
|
+
val == true
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "vagrant"
|
4
|
+
require "i18n"
|
5
|
+
|
6
|
+
require_relative "version"
|
7
|
+
require_relative "helpers"
|
8
|
+
require_relative "command"
|
9
|
+
require_relative "config"
|
10
|
+
require_relative "network_builder"
|
11
|
+
require_relative "action"
|
12
|
+
|
13
|
+
module VagrantDockerNetworksManager
|
14
|
+
class Plugin < Vagrant.plugin("2")
|
15
|
+
name "docker_networks_manager"
|
16
|
+
|
17
|
+
config(:docker_network) do
|
18
|
+
VagrantDockerNetworksManager::Config
|
19
|
+
end
|
20
|
+
|
21
|
+
command "network" do
|
22
|
+
VagrantDockerNetworksManager::Command
|
23
|
+
end
|
24
|
+
|
25
|
+
action_hook(:create_docker_network, :machine_action_up) do |hook|
|
26
|
+
hook.prepend(ActionUp)
|
27
|
+
end
|
28
|
+
|
29
|
+
action_hook(:cleanup_docker_network, :machine_action_destroy) do |hook|
|
30
|
+
hook.append(ActionDestroy)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "open3"
|
4
|
+
require "json"
|
5
|
+
require "ipaddr"
|
6
|
+
require "shellwords"
|
7
|
+
|
8
|
+
module VagrantDockerNetworksManager
|
9
|
+
module Util
|
10
|
+
module_function
|
11
|
+
|
12
|
+
def sh!(*args)
|
13
|
+
if ENV["VDNM_VERBOSE"] == "1"
|
14
|
+
printable = ["docker", *args].map(&:to_s).shelljoin
|
15
|
+
$stderr.puts("[VDNM] #{printable}")
|
16
|
+
system("docker", *args)
|
17
|
+
else
|
18
|
+
system("docker", *args, out: File::NULL, err: :out)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def docker_available?
|
23
|
+
_out, _err, status = Open3.capture3("docker", "info")
|
24
|
+
status.success?
|
25
|
+
rescue
|
26
|
+
false
|
27
|
+
end
|
28
|
+
|
29
|
+
def docker_network_exists?(name)
|
30
|
+
out, _err, st = Open3.capture3("docker", "network", "ls", "--format", "{{.Name}}")
|
31
|
+
st.success? && out.split.include?(name)
|
32
|
+
end
|
33
|
+
|
34
|
+
def read_network_labels(name)
|
35
|
+
out, _err, st = Open3.capture3("docker", "network", "inspect", name, "--format", "{{json .Labels}}")
|
36
|
+
return {} unless st.success?
|
37
|
+
JSON.parse(out.to_s.strip) rescue {}
|
38
|
+
end
|
39
|
+
|
40
|
+
def inspect_networks_batched(ids_or_names)
|
41
|
+
result = {}
|
42
|
+
ids_or_names.each_slice(50) do |chunk|
|
43
|
+
out, _e, st = Open3.capture3("docker", "network", "inspect", *chunk)
|
44
|
+
next unless st.success?
|
45
|
+
JSON.parse(out).each do |net|
|
46
|
+
subs = (net.dig("IPAM","Config") || []).map { |c| c["Subnet"] }.compact
|
47
|
+
cons = (net["Containers"] || {}).size
|
48
|
+
key = net["Id"] || net["Name"]
|
49
|
+
result[key] = { subnets: subs, containers_count: cons }
|
50
|
+
result[net["Name"]] ||= result[key]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
result
|
54
|
+
rescue
|
55
|
+
{}
|
56
|
+
end
|
57
|
+
|
58
|
+
def list_plugin_networks
|
59
|
+
out, _err, st = Open3.capture3(
|
60
|
+
"docker", "network", "ls",
|
61
|
+
"--filter", "label=com.vagrant.plugin=docker_networks_manager",
|
62
|
+
"--format", "{{.ID}}\t{{.Name}}\t{{.Driver}}\t{{.Scope}}"
|
63
|
+
)
|
64
|
+
return [] unless st.success?
|
65
|
+
|
66
|
+
rows = out.lines.map do |line|
|
67
|
+
id, name, driver, scope = line.strip.split("\t", 4)
|
68
|
+
{ id: id, name: name, driver: driver, scope: scope }
|
69
|
+
end
|
70
|
+
|
71
|
+
details = inspect_networks_batched(rows.map { |r| r[:name] })
|
72
|
+
rows.each do |r|
|
73
|
+
subs = details.dig(r[:name], :subnets) || []
|
74
|
+
r[:subnets] = subs.empty? ? "-" : subs.join(", ")
|
75
|
+
end
|
76
|
+
|
77
|
+
rows
|
78
|
+
rescue
|
79
|
+
[]
|
80
|
+
end
|
81
|
+
|
82
|
+
def list_plugin_networks_detailed
|
83
|
+
out, _err, st = Open3.capture3(
|
84
|
+
"docker", "network", "ls",
|
85
|
+
"--filter", "label=com.vagrant.plugin=docker_networks_manager",
|
86
|
+
"--format", "{{.ID}}\t{{.Name}}\t{{.Driver}}\t{{.Scope}}"
|
87
|
+
)
|
88
|
+
return [] unless st.success?
|
89
|
+
|
90
|
+
rows = out.lines.map { |line|
|
91
|
+
id, name, driver, scope = line.strip.split("\t", 4)
|
92
|
+
{ id: id, name: name, driver: driver, scope: scope }
|
93
|
+
}
|
94
|
+
|
95
|
+
details = inspect_networks_batched(rows.map { |r| r[:name] })
|
96
|
+
rows.each do |r|
|
97
|
+
r[:subnets] = details.dig(r[:name], :subnets) || []
|
98
|
+
r[:containers] = details.dig(r[:name], :containers_count) || 0
|
99
|
+
end
|
100
|
+
rows
|
101
|
+
end
|
102
|
+
|
103
|
+
def valid_subnet?(cidr)
|
104
|
+
ip_str, mask_str = cidr.to_s.split("/", 2)
|
105
|
+
return false unless ip_str && mask_str && mask_str =~ /^\d+$/ && (0..32).include?(mask_str.to_i)
|
106
|
+
ip = IPAddr.new(ip_str) rescue nil
|
107
|
+
return false unless ip && ip.ipv4?
|
108
|
+
(ip.mask(mask_str.to_i).to_s == ip_str)
|
109
|
+
rescue
|
110
|
+
false
|
111
|
+
end
|
112
|
+
|
113
|
+
def normalize_cidr(cidr)
|
114
|
+
ip_str, mask_str = cidr.to_s.split("/", 2)
|
115
|
+
return nil unless ip_str && mask_str && mask_str =~ /^\d+$/ && (0..32).include?(mask_str.to_i)
|
116
|
+
ip = IPAddr.new(ip_str) rescue nil
|
117
|
+
return nil unless ip&.ipv4?
|
118
|
+
"#{ip.mask(mask_str.to_i)}/#{mask_str.to_i}"
|
119
|
+
rescue
|
120
|
+
nil
|
121
|
+
end
|
122
|
+
|
123
|
+
def cidr_overlap?(a, b)
|
124
|
+
ip_a, mask_a = a.to_s.split("/", 2); ip_b, mask_b = b.to_s.split("/", 2)
|
125
|
+
return false unless mask_a && mask_b
|
126
|
+
na = IPAddr.new(ip_a).mask(mask_a.to_i)
|
127
|
+
nb = IPAddr.new(ip_b).mask(mask_b.to_i)
|
128
|
+
na.include?(IPAddr.new(ip_b)) || nb.include?(IPAddr.new(ip_a))
|
129
|
+
rescue
|
130
|
+
false
|
131
|
+
end
|
132
|
+
|
133
|
+
def each_docker_cidr(ignore_network: nil)
|
134
|
+
out, _e, st = Open3.capture3("docker", "network", "ls", "-q")
|
135
|
+
return [] unless st.success?
|
136
|
+
out.split.each_slice(50).flat_map do |chunk|
|
137
|
+
o, _e2, st2 = Open3.capture3("docker", "network", "inspect", *chunk)
|
138
|
+
next [] unless st2.success?
|
139
|
+
JSON.parse(o).filter_map do |net|
|
140
|
+
next if ignore_network && net["Name"] == ignore_network
|
141
|
+
(net.dig("IPAM","Config") || []).map { |cfg| normalize_cidr(cfg["Subnet"]) }.compact
|
142
|
+
end.flatten
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def docker_subnet_conflicts?(target_cidr, ignore_network: nil)
|
147
|
+
t_norm = normalize_cidr(target_cidr)
|
148
|
+
return false unless t_norm
|
149
|
+
each_docker_cidr(ignore_network: ignore_network).any? { |c| cidr_overlap?(t_norm, c) }
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
data/locales/en.yml
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
en:
|
2
|
+
emoji:
|
3
|
+
success: "✅"
|
4
|
+
info: "🔍"
|
5
|
+
ongoing: "🔁"
|
6
|
+
warning: "⚠️"
|
7
|
+
error: "❌"
|
8
|
+
version: "💾"
|
9
|
+
broom: "🧹"
|
10
|
+
question: "❓"
|
11
|
+
messages:
|
12
|
+
network_exists_adopted: "Docker network '%{name}' already exists (adopted by this project)."
|
13
|
+
network_exists: "Docker network '%{name}' already exists."
|
14
|
+
create_failed: "Failed to create network with: %{cmd}."
|
15
|
+
remove_network: "Removing Docker network '%{name}'."
|
16
|
+
nothing_to_do: "Nothing to do (no network created by this 'up')."
|
17
|
+
not_owned: "Network '%{name}' is not marked by this plugin, ignoring."
|
18
|
+
no_networks: "No networks managed by the plugin."
|
19
|
+
networks_header: "Docker networks managed by the plugin:"
|
20
|
+
prune_none: "No network to remove."
|
21
|
+
confirm_continue: "%{prompt} Continue? [y/N]"
|
22
|
+
usage:
|
23
|
+
init: "Usage: vagrant network init <name> <subnet>"
|
24
|
+
destroy: "Usage: vagrant network destroy <name> [--with-containers]"
|
25
|
+
reload: "Usage: vagrant network reload <name>"
|
26
|
+
info: "Usage: vagrant network info <name>"
|
27
|
+
list: "Usage: vagrant network list"
|
28
|
+
prune: "Usage: vagrant network prune"
|
29
|
+
rename: "Usage: vagrant network rename <old> <new> [subnet]"
|
30
|
+
errors:
|
31
|
+
docker_unavailable: "Docker is unavailable. Make sure it is installed and running."
|
32
|
+
unknown_command: "Unknown command. Try: vagrant network help."
|
33
|
+
network_exists: "Network already exists."
|
34
|
+
network_not_found: "Network not found."
|
35
|
+
invalid_subnet: "Invalid subnet."
|
36
|
+
subnet_in_use: "Subnet already in use or overlapping."
|
37
|
+
cancelled: "Cancelled by user."
|
38
|
+
inspect_failed: "docker network inspect failed."
|
39
|
+
create_failed: "Failed to create network."
|
40
|
+
remove_failed: "Failed to remove network."
|
41
|
+
target_exists: "Target network already exists."
|
42
|
+
partial_failure: "Partial failure."
|
43
|
+
invalid_name: "Invalid network name. Must be 1-127 characters, start with a letter or digit, and contain only letters, digits, underscores (_), periods (.), or hyphens (-)."
|
44
|
+
invalid_type: "network_type must be 'bridge' or 'macvlan'."
|
45
|
+
invalid_gateway: "network_gateway must be an IPv4 string."
|
46
|
+
invalid_parent: "network_parent must be a String (host interface)."
|
47
|
+
invalid_attachable: "network_attachable must be boolean."
|
48
|
+
invalid_ipv6: "enable_ipv6 must be boolean."
|
49
|
+
invalid_ip_range: "ip_range must be an IPv4/CIDR string."
|
50
|
+
invalid_cleanup: "cleanup_on_destroy must be boolean."
|
51
|
+
invalid_locale: "locale must be 'fr' or 'en'."
|
52
|
+
prompts:
|
53
|
+
delete_network: "This will delete network '%{name}'."
|
54
|
+
delete_network_only: "This will delete network '%{name}' (containers will be left untouched)."
|
55
|
+
delete_network_with_containers: "This will delete network '%{name}' and remove %{count} attached container(s)."
|
56
|
+
reload_network: "This will delete and recreate '%{name}'."
|
57
|
+
rename_network: "Rename '%{old}' → '%{new}'?"
|
58
|
+
rename_same_subnet: "Rename '%{old}' → '%{new}'?"
|
59
|
+
reload_same: "Reload the network '%{name}' with the same subnet and reconnect its containers."
|
60
|
+
prune: "Delete %{count} unused network(s)?"
|
61
|
+
log:
|
62
|
+
create_network: "Create network '%{name}' (subnet: %{subnet})."
|
63
|
+
remove_network: "Remove network '%{name}'."
|
64
|
+
remove_container: "Remove container '%{name}'."
|
65
|
+
disconnect_container: "Disconnect container '%{name}'."
|
66
|
+
subnet_changed: "Subnet changed — containers not auto-reconnected."
|
67
|
+
info_header: "Info for '%{name}':"
|
68
|
+
version_line: "v%{version}."
|
69
|
+
ok: "OK."
|
70
|
+
docker_provider:
|
71
|
+
network_create: "Creating network…"
|
72
|
+
creating: "Creating Docker container…"
|
73
|
+
created: "Container created."
|
74
|
+
network_connect: "Connecting container to network…"
|
75
|
+
messages:
|
76
|
+
starting: "Starting container…"
|
77
|
+
help:
|
78
|
+
general_title: "Usage: vagrant network <command> [args] [options]"
|
79
|
+
commands:
|
80
|
+
init: "network init <name> <subnet>"
|
81
|
+
destroy: "network destroy <name>"
|
82
|
+
reload: "network reload <name>"
|
83
|
+
info: "network info <name>"
|
84
|
+
list: "network list"
|
85
|
+
prune: "network prune"
|
86
|
+
rename: "network rename <old> <new> [<subnet>]"
|
87
|
+
version: "network version"
|
88
|
+
topic:
|
89
|
+
init: |
|
90
|
+
🔧 Help: vagrant network init
|
91
|
+
Usage:
|
92
|
+
vagrant network init <name> <subnet>
|
93
|
+
Description:
|
94
|
+
Creates a bridge Docker network with IPv4/CIDR, validates format and conflicts.
|
95
|
+
Examples:
|
96
|
+
vagrant network init mynet 172.28.100.0/26
|
97
|
+
destroy: |
|
98
|
+
🗑️ Help: vagrant network destroy
|
99
|
+
Usage:
|
100
|
+
vagrant network destroy <name> [--with-containers] [--yes]
|
101
|
+
Description:
|
102
|
+
Deletes the Docker network. By default, containers are only disconnected.
|
103
|
+
With --with-containers, attached containers are also removed (docker rm -f).
|
104
|
+
reload: |
|
105
|
+
🔁 Help: vagrant network reload
|
106
|
+
Usage:
|
107
|
+
vagrant network reload <name> [--yes]
|
108
|
+
Description:
|
109
|
+
Recreates the network with same subnets and tries to reconnect containers.
|
110
|
+
Returns partial failure if some containers fail to reconnect.
|
111
|
+
rename: |
|
112
|
+
✏️ Help: vagrant network rename
|
113
|
+
Usage:
|
114
|
+
vagrant network rename <old> <new> [<subnet>] [--yes]
|
115
|
+
Description:
|
116
|
+
If subnet changes or name is same, old network is removed/recreated.
|
117
|
+
Fails if removing the old network is not possible.
|
118
|
+
prune: |
|
119
|
+
🧹 Help: vagrant network prune
|
120
|
+
Usage:
|
121
|
+
vagrant network prune [--yes]
|
122
|
+
Description:
|
123
|
+
Removes plugin-managed networks without attached containers.
|
124
|
+
list: |
|
125
|
+
📋 Help: vagrant network list
|
126
|
+
Usage:
|
127
|
+
vagrant network list [--json]
|
128
|
+
Description:
|
129
|
+
Lists plugin-managed networks with driver/scope and subnets.
|
130
|
+
version: |
|
131
|
+
💾 Help: vagrant network version
|
132
|
+
Usage:
|
133
|
+
vagrant network version [--json]
|
134
|
+
Description:
|
135
|
+
Prints the plugin version. With --json, prints a normalized JSON payload.
|
136
|
+
info: |
|
137
|
+
🔍 Help: vagrant network info
|
138
|
+
Usage:
|
139
|
+
vagrant network info <name> [--json]
|
140
|
+
Description:
|
141
|
+
Shows details for a Docker network: driver, subnet(s), and connected containers.
|
142
|
+
With --json, prints a normalized JSON payload.
|