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.
@@ -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
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module VagrantDockerNetworksManager
4
+ VERSION = begin
5
+ path = File.expand_path("VERSION", __dir__)
6
+ File.exist?(path) ? File.read(path).strip : "0.1.0"
7
+ rescue
8
+ "0.1.0"
9
+ end
10
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "vagrant-docker-networks-manager/version"
4
+ require_relative "vagrant-docker-networks-manager/plugin"
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.