vagrant-docker-hosts-manager 0.3.0 → 0.4.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 +4 -4
- data/CHANGELOG.md +13 -0
- data/lib/vagrant-docker-hosts-manager/VERSION +1 -1
- data/lib/vagrant-docker-hosts-manager/actions/apply.rb +68 -0
- data/lib/vagrant-docker-hosts-manager/actions/cleanup.rb +34 -0
- data/lib/vagrant-docker-hosts-manager/command.rb +254 -247
- data/lib/vagrant-docker-hosts-manager/config.rb +39 -7
- data/lib/vagrant-docker-hosts-manager/helpers.rb +16 -6
- data/lib/vagrant-docker-hosts-manager/plugin.rb +2 -90
- data/lib/vagrant-docker-hosts-manager/util/docker.rb +9 -13
- data/lib/vagrant-docker-hosts-manager/util/hosts_file.rb +114 -112
- data/lib/vagrant-docker-hosts-manager/util/i18n.rb +1 -1
- data/lib/vagrant-docker-hosts-manager/util/verbose.rb +22 -0
- data/locales/en.yml +117 -111
- data/locales/fr.yml +118 -112
- metadata +6 -2
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module VagrantDockerHostsManager
|
|
4
|
+
# Vagrant configuration for managed host entries.
|
|
5
|
+
#
|
|
6
|
+
# @!attribute domains
|
|
7
|
+
# @return [Hash{String=>String}] Mapping of domain names to IP addresses.
|
|
8
|
+
# @!attribute domain
|
|
9
|
+
# @return [String, nil] Single domain to resolve from `ip` or `container_name`.
|
|
10
|
+
# @!attribute container_name
|
|
11
|
+
# @return [String, nil] Docker container used for automatic IP discovery.
|
|
12
|
+
# @!attribute ip
|
|
13
|
+
# @return [String, nil] Static IPv4 address for `domain`.
|
|
14
|
+
# @!attribute locale
|
|
15
|
+
# @return [String, nil] Optional locale code.
|
|
4
16
|
class Config < Vagrant.plugin("2", :config)
|
|
5
17
|
attr_accessor :domains
|
|
6
18
|
attr_accessor :domain
|
|
@@ -10,19 +22,28 @@ module VagrantDockerHostsManager
|
|
|
10
22
|
attr_accessor :verbose
|
|
11
23
|
|
|
12
24
|
def initialize
|
|
13
|
-
@domains =
|
|
14
|
-
@domain =
|
|
15
|
-
@container_name =
|
|
16
|
-
@ip =
|
|
17
|
-
@locale =
|
|
18
|
-
@verbose =
|
|
25
|
+
@domains = UNSET_VALUE
|
|
26
|
+
@domain = UNSET_VALUE
|
|
27
|
+
@container_name = UNSET_VALUE
|
|
28
|
+
@ip = UNSET_VALUE
|
|
29
|
+
@locale = UNSET_VALUE
|
|
30
|
+
@verbose = UNSET_VALUE
|
|
19
31
|
end
|
|
20
32
|
|
|
21
|
-
def finalize
|
|
33
|
+
def finalize!
|
|
34
|
+
@domains = {} if @domains == UNSET_VALUE
|
|
35
|
+
@domain = nil if @domain == UNSET_VALUE
|
|
36
|
+
@container_name = nil if @container_name == UNSET_VALUE
|
|
37
|
+
@ip = nil if @ip == UNSET_VALUE
|
|
38
|
+
@locale = nil if @locale == UNSET_VALUE
|
|
39
|
+
@verbose = false if @verbose == UNSET_VALUE
|
|
40
|
+
end
|
|
22
41
|
|
|
23
42
|
def validate(_machine)
|
|
24
43
|
errors = []
|
|
25
44
|
|
|
45
|
+
return { "vagrant-docker-hosts-manager" => errors } unless configured?
|
|
46
|
+
|
|
26
47
|
if (@domains.nil? || @domains.empty?) && (@domain.nil? || @domain.strip.empty?)
|
|
27
48
|
errors << "You must configure at least one domain: " \
|
|
28
49
|
"`config.docker_hosts.domain = \"example.test\"` or set " \
|
|
@@ -43,5 +64,16 @@ module VagrantDockerHostsManager
|
|
|
43
64
|
|
|
44
65
|
{ "vagrant-docker-hosts-manager" => errors }
|
|
45
66
|
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def configured?
|
|
71
|
+
(@domains.is_a?(Hash) && !@domains.empty?) ||
|
|
72
|
+
present?(@domain) || present?(@container_name) || present?(@ip)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def present?(value)
|
|
76
|
+
!value.nil? && !value.to_s.strip.empty?
|
|
77
|
+
end
|
|
46
78
|
end
|
|
47
79
|
end
|
|
@@ -9,6 +9,7 @@ module VagrantDockerHostsManager
|
|
|
9
9
|
|
|
10
10
|
SUPPORTED = [:en, :fr].freeze
|
|
11
11
|
OUR_NAMESPACES = %w[messages. errors. log. help.].freeze
|
|
12
|
+
NS = "vdhm"
|
|
12
13
|
|
|
13
14
|
EMOJI = {
|
|
14
15
|
success: "✅",
|
|
@@ -31,6 +32,9 @@ module VagrantDockerHostsManager
|
|
|
31
32
|
|
|
32
33
|
base = File.expand_path("../../locales", __dir__)
|
|
33
34
|
paths = Dir[File.join(base, "*.yml")]
|
|
35
|
+
if paths.empty? && File.directory?(base)
|
|
36
|
+
paths = Dir.children(base).grep(/\.ya?ml\z/).map { |file| File.join(base, file) }
|
|
37
|
+
end
|
|
34
38
|
::I18n.load_path |= paths
|
|
35
39
|
::I18n.available_locales = SUPPORTED
|
|
36
40
|
|
|
@@ -60,22 +64,28 @@ module VagrantDockerHostsManager
|
|
|
60
64
|
set_locale!("en")
|
|
61
65
|
end
|
|
62
66
|
|
|
67
|
+
def namespaced(key)
|
|
68
|
+
k = key.to_s
|
|
69
|
+
k.start_with?("#{NS}.") ? k : "#{NS}.#{k}"
|
|
70
|
+
end
|
|
71
|
+
|
|
63
72
|
def t(key, **opts)
|
|
64
|
-
|
|
73
|
+
setup_i18n!
|
|
74
|
+
::I18n.t(namespaced(key), **opts)
|
|
65
75
|
end
|
|
66
76
|
|
|
67
77
|
def t!(key, **opts)
|
|
68
78
|
setup_i18n!
|
|
69
|
-
|
|
70
|
-
if our_key?(
|
|
71
|
-
raise MissingTranslationError, "#{EMOJI[:error]} [#{::I18n.locale}] Missing translation for key: #{
|
|
79
|
+
nk = namespaced(key)
|
|
80
|
+
if our_key?(key.to_s) && !::I18n.exists?(nk, ::I18n.locale)
|
|
81
|
+
raise MissingTranslationError, "#{EMOJI[:error]} [#{::I18n.locale}] Missing translation for key: #{nk}"
|
|
72
82
|
end
|
|
73
|
-
::I18n.t(
|
|
83
|
+
::I18n.t(nk, **opts)
|
|
74
84
|
end
|
|
75
85
|
|
|
76
86
|
def t_hash(key)
|
|
77
87
|
setup_i18n!
|
|
78
|
-
v = ::I18n.t(key, default: {})
|
|
88
|
+
v = ::I18n.t(namespaced(key), default: {})
|
|
79
89
|
v.is_a?(Hash) ? v : {}
|
|
80
90
|
end
|
|
81
91
|
|
|
@@ -11,6 +11,8 @@ require_relative "util/hosts_file"
|
|
|
11
11
|
require_relative "util/docker"
|
|
12
12
|
require_relative "util/json"
|
|
13
13
|
require_relative "util/i18n"
|
|
14
|
+
require_relative "actions/apply"
|
|
15
|
+
require_relative "actions/cleanup"
|
|
14
16
|
|
|
15
17
|
begin
|
|
16
18
|
I18n.enforce_available_locales = false
|
|
@@ -47,94 +49,4 @@ module VagrantDockerHostsManager
|
|
|
47
49
|
hook.prepend(Action::Cleanup)
|
|
48
50
|
end
|
|
49
51
|
end
|
|
50
|
-
|
|
51
|
-
module Action
|
|
52
|
-
class Apply
|
|
53
|
-
def initialize(app, env) = (@app = app)
|
|
54
|
-
|
|
55
|
-
def call(env)
|
|
56
|
-
Util::I18n.setup!(env)
|
|
57
|
-
cfg = env[:machine].config.docker_hosts
|
|
58
|
-
mid = env[:machine].id || "unknown"
|
|
59
|
-
dry = Util::I18n.env_flag("VDHM_DRY_RUN")
|
|
60
|
-
ui = env[:ui]
|
|
61
|
-
hoster = Util::HostsFile.new(env, owner_id: mid)
|
|
62
|
-
|
|
63
|
-
entries = compute_entries(env, cfg, ui)
|
|
64
|
-
if entries.empty?
|
|
65
|
-
UiHelpers.say(ui, ::I18n.t("messages.no_entries"))
|
|
66
|
-
return
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
if dry
|
|
70
|
-
Util::Json.emit(action: "apply", status: "dry-run", data: { owner: mid, entries: entries })
|
|
71
|
-
return
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
hoster.apply(entries)
|
|
75
|
-
Util::Json.emit(action: "apply", status: "success", data: { owner: mid, entries: entries })
|
|
76
|
-
rescue StandardError => e
|
|
77
|
-
Util::Json.emit(action: "apply", status: "error", error: e.message, backtrace: e.backtrace&.first(3))
|
|
78
|
-
UiHelpers.error(ui, "VDHM: #{e.message}")
|
|
79
|
-
ensure
|
|
80
|
-
@app.call(env)
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
private
|
|
84
|
-
|
|
85
|
-
def compute_entries(env, cfg, ui)
|
|
86
|
-
entries = {}
|
|
87
|
-
|
|
88
|
-
cfg.domains.each do |domain, ip|
|
|
89
|
-
next if domain.to_s.strip.empty?
|
|
90
|
-
if ip.nil? || ip.to_s.strip.empty?
|
|
91
|
-
UiHelpers.warn(ui, ::I18n.t("messages.missing_ip_for", domain: domain))
|
|
92
|
-
next
|
|
93
|
-
end
|
|
94
|
-
entries[domain] = ip
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
if cfg.domain && !cfg.domain.strip.empty?
|
|
98
|
-
ip = cfg.ip || begin
|
|
99
|
-
if cfg.container_name && !cfg.container_name.strip.empty?
|
|
100
|
-
Util::Docker.ip_for_container(cfg.container_name)
|
|
101
|
-
end
|
|
102
|
-
end
|
|
103
|
-
if ip && !ip.strip.empty?
|
|
104
|
-
UiHelpers.say(ui, ::I18n.t("messages.detected_ip", domain: cfg.domain, ip: ip))
|
|
105
|
-
entries[cfg.domain] = ip
|
|
106
|
-
else
|
|
107
|
-
UiHelpers.warn(ui, ::I18n.t("messages.no_ip_found", domain: cfg.domain, container: cfg.container_name))
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
entries
|
|
112
|
-
end
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
class Cleanup
|
|
116
|
-
def initialize(app, env) = (@app = app)
|
|
117
|
-
|
|
118
|
-
def call(env)
|
|
119
|
-
Util::I18n.setup!(env)
|
|
120
|
-
mid = env[:machine].id || "unknown"
|
|
121
|
-
dry = Util::I18n.env_flag("VDHM_DRY_RUN")
|
|
122
|
-
ui = env[:ui]
|
|
123
|
-
hoster = Util::HostsFile.new(env, owner_id: mid)
|
|
124
|
-
|
|
125
|
-
if dry
|
|
126
|
-
Util::Json.emit(action: "cleanup", status: "dry-run", data: { owner: mid })
|
|
127
|
-
return
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
removed = hoster.remove!
|
|
131
|
-
Util::Json.emit(action: "cleanup", status: "success", data: { owner: mid, removed: removed })
|
|
132
|
-
rescue StandardError => e
|
|
133
|
-
Util::Json.emit(action: "cleanup", status: "error", error: e.message)
|
|
134
|
-
UiHelpers.error(ui, "VDHM: #{e.message}")
|
|
135
|
-
ensure
|
|
136
|
-
@app.call(env)
|
|
137
|
-
end
|
|
138
|
-
end
|
|
139
|
-
end
|
|
140
52
|
end
|
|
@@ -1,32 +1,28 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "open3"
|
|
4
|
+
require_relative "verbose"
|
|
4
5
|
|
|
5
6
|
module VagrantDockerHostsManager
|
|
6
7
|
module Util
|
|
7
8
|
module Docker
|
|
8
9
|
module_function
|
|
9
10
|
|
|
11
|
+
# Resolves the first IPv4 address exposed by Docker inspect for a container.
|
|
12
|
+
#
|
|
13
|
+
# @param name [String, #to_s] Docker container name or id.
|
|
14
|
+
# @return [String, nil] First IPv4 address, or nil when Docker cannot resolve it.
|
|
10
15
|
def ip_for_container(name)
|
|
11
16
|
return nil if name.to_s.strip.empty?
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
)
|
|
17
|
+
|
|
18
|
+
fmt = "{{range .NetworkSettings.Networks}}{{.IPAddress}} {{end}}"
|
|
19
|
+
Verbose.log("docker", "inspect", "-f", fmt, name.to_s)
|
|
20
|
+
out, _err, status = Open3.capture3("docker", "inspect", "-f", fmt, name.to_s)
|
|
17
21
|
return nil unless status.success?
|
|
18
22
|
out.split(/\s+/).find { |ip| ip =~ /\A\d{1,3}(\.\d{1,3}){3}\z/ }
|
|
19
23
|
rescue StandardError
|
|
20
24
|
nil
|
|
21
25
|
end
|
|
22
|
-
|
|
23
|
-
def shell_escape(str)
|
|
24
|
-
if Gem.win_platform?
|
|
25
|
-
%('#{str.to_s.gsub("'", "''")}')
|
|
26
|
-
else
|
|
27
|
-
%('#{str.to_s.gsub("'", "'\\''")}')
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
26
|
end
|
|
31
27
|
end
|
|
32
28
|
end
|
|
@@ -7,6 +7,7 @@ require "open3"
|
|
|
7
7
|
|
|
8
8
|
require_relative "../helpers"
|
|
9
9
|
require_relative "docker"
|
|
10
|
+
require_relative "verbose"
|
|
10
11
|
|
|
11
12
|
module VagrantDockerHostsManager
|
|
12
13
|
module Util
|
|
@@ -16,6 +17,10 @@ module VagrantDockerHostsManager
|
|
|
16
17
|
WIN_SYSNATIVE_PATH = "C:/Windows/Sysnative/drivers/etc/hosts"
|
|
17
18
|
WIN_SYSWOW64_PATH = "C:/Windows/SysWOW64/drivers/etc/hosts"
|
|
18
19
|
|
|
20
|
+
BLOCK_START_RE = /^\s*#\s*>>>\s*vagrant-docker-hosts-manager\s+(.+?)\s*\(managed\)\s*>>>\s*$/i
|
|
21
|
+
BLOCK_STOP_RE = /^\s*#\s*<<<\s*vagrant-docker-hosts-manager\s+(.+?)\s*\(managed\)\s*<<<\s*$/i
|
|
22
|
+
ENTRY_RE = /\A\s*(\d{1,3}(?:\.\d{1,3}){3})\s+([^\s#]+)/
|
|
23
|
+
|
|
19
24
|
def initialize(env, owner_id:)
|
|
20
25
|
@env = env || {}
|
|
21
26
|
@owner_id = owner_id.to_s
|
|
@@ -28,8 +33,13 @@ module VagrantDockerHostsManager
|
|
|
28
33
|
end
|
|
29
34
|
|
|
30
35
|
def path_candidates
|
|
31
|
-
|
|
36
|
+
# VDHM_HOSTS_PATH overrides the default location on every platform
|
|
37
|
+
# (used by tests and power users); honor it before anything else.
|
|
32
38
|
return [override_path] if override_path
|
|
39
|
+
return [POSIX_PATH] unless Gem.win_platform?
|
|
40
|
+
|
|
41
|
+
# Sysnative lets a 32-bit Ruby process reach the real System32 hosts
|
|
42
|
+
# file on 64-bit Windows; keep it before System32/SysWOW64.
|
|
33
43
|
[WIN_SYSNATIVE_PATH, WIN_SYS32_PATH, WIN_SYSWOW64_PATH]
|
|
34
44
|
end
|
|
35
45
|
|
|
@@ -103,11 +113,20 @@ module VagrantDockerHostsManager
|
|
|
103
113
|
str.end_with?(nl) ? str : (str + nl)
|
|
104
114
|
end
|
|
105
115
|
|
|
116
|
+
# Applies managed host entries to the hosts file.
|
|
117
|
+
#
|
|
118
|
+
# Rewrites only this plugin's managed block and preserves unmanaged lines.
|
|
119
|
+
# Entries are normalized and sorted so repeated runs converge to the same
|
|
120
|
+
# file content.
|
|
121
|
+
#
|
|
122
|
+
# @param entries [Hash{String=>String}] Mapping of FQDN to IP address.
|
|
123
|
+
# @return [Integer] Number of managed entries present after applying changes.
|
|
124
|
+
# @raise [RuntimeError] When elevated writes fail.
|
|
106
125
|
def apply(entries)
|
|
107
126
|
entries = normalize_entries(entries)
|
|
108
127
|
if entries.empty?
|
|
109
128
|
UiHelpers.say(@ui, "#{UiHelpers.e(:info)} " +
|
|
110
|
-
::I18n.t("messages.no_entries", default: "No hosts entries configured."))
|
|
129
|
+
::I18n.t("vdhm.messages.no_entries", default: "No hosts entries configured."))
|
|
111
130
|
return 0
|
|
112
131
|
end
|
|
113
132
|
|
|
@@ -137,7 +156,7 @@ module VagrantDockerHostsManager
|
|
|
137
156
|
|
|
138
157
|
if added.empty? && updated.empty?
|
|
139
158
|
UiHelpers.say(@ui, "#{UiHelpers.e(:info)} " +
|
|
140
|
-
::I18n.t("messages.no_change", default: "Nothing to apply. Already up-to-date."))
|
|
159
|
+
::I18n.t("vdhm.messages.no_change", default: "Nothing to apply. Already up-to-date."))
|
|
141
160
|
return existing_map.size
|
|
142
161
|
end
|
|
143
162
|
|
|
@@ -146,14 +165,21 @@ module VagrantDockerHostsManager
|
|
|
146
165
|
write(content)
|
|
147
166
|
|
|
148
167
|
UiHelpers.say(@ui, "#{UiHelpers.e(:success)} " +
|
|
149
|
-
::I18n.t("messages.applied", default: "Hosts entries applied."))
|
|
168
|
+
::I18n.t("vdhm.messages.applied", default: "Hosts entries applied."))
|
|
150
169
|
UiHelpers.say(@ui, "#{UiHelpers.e(:info)} " +
|
|
151
|
-
::I18n.t("messages.apply_summary",
|
|
170
|
+
::I18n.t("vdhm.messages.apply_summary",
|
|
152
171
|
default: "Added: %{a}, Updated: %{u}, Unchanged: %{s}",
|
|
153
172
|
a: added.size, u: updated.size, s: unchanged.size))
|
|
154
173
|
merged.size
|
|
155
174
|
end
|
|
156
175
|
|
|
176
|
+
# Removes matching entries from the current owner's managed block.
|
|
177
|
+
#
|
|
178
|
+
# With no filters this falls back to removing the whole current-owner block.
|
|
179
|
+
#
|
|
180
|
+
# @param ips [Array<String>] IP addresses to remove.
|
|
181
|
+
# @param domains [Array<String>] Domain names to remove.
|
|
182
|
+
# @return [Integer] Number of removed entries, or 1 when a whole block was removed.
|
|
157
183
|
def remove_entries!(ips: [], domains: [])
|
|
158
184
|
ips = Array(ips).map(&:to_s).reject(&:empty?)
|
|
159
185
|
domains = Array(domains).map(&:to_s).reject(&:empty?)
|
|
@@ -170,7 +196,7 @@ module VagrantDockerHostsManager
|
|
|
170
196
|
removed_count = before - filtered.length
|
|
171
197
|
if removed_count <= 0
|
|
172
198
|
UiHelpers.say(@ui, "#{UiHelpers.e(:info)} " +
|
|
173
|
-
::I18n.t("messages.remove_none",
|
|
199
|
+
::I18n.t("vdhm.messages.remove_none",
|
|
174
200
|
default: "No matching entry to remove."))
|
|
175
201
|
return 0
|
|
176
202
|
end
|
|
@@ -192,7 +218,7 @@ module VagrantDockerHostsManager
|
|
|
192
218
|
@ui,
|
|
193
219
|
"#{UiHelpers.e(:broom)} " +
|
|
194
220
|
::I18n.t(
|
|
195
|
-
"messages.removed_count",
|
|
221
|
+
"vdhm.messages.removed_count",
|
|
196
222
|
default: "%{count} entries removed.",
|
|
197
223
|
count: removed_count
|
|
198
224
|
)
|
|
@@ -200,6 +226,9 @@ module VagrantDockerHostsManager
|
|
|
200
226
|
removed_count
|
|
201
227
|
end
|
|
202
228
|
|
|
229
|
+
# Removes the current owner's managed hosts block.
|
|
230
|
+
#
|
|
231
|
+
# @return [Boolean] Whether a block was removed.
|
|
203
232
|
def remove!
|
|
204
233
|
content = read
|
|
205
234
|
newc = remove_block_from(content)
|
|
@@ -207,12 +236,44 @@ module VagrantDockerHostsManager
|
|
|
207
236
|
write(newc) if removed
|
|
208
237
|
|
|
209
238
|
UiHelpers.say(@ui, removed ?
|
|
210
|
-
"#{UiHelpers.e(:broom)} " + ::I18n.t("messages.cleaned", default: "Managed hosts entries removed.") :
|
|
211
|
-
"#{UiHelpers.e(:info)} " + ::I18n.t("messages.nothing_to_clean", default: "Nothing to clean."))
|
|
239
|
+
"#{UiHelpers.e(:broom)} " + ::I18n.t("vdhm.messages.cleaned", default: "Managed hosts entries removed.") :
|
|
240
|
+
"#{UiHelpers.e(:info)} " + ::I18n.t("vdhm.messages.nothing_to_clean", default: "Nothing to clean."))
|
|
241
|
+
removed
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Removes every block managed by this plugin, regardless of owner.
|
|
245
|
+
#
|
|
246
|
+
# @return [Boolean] Whether any managed block was removed.
|
|
247
|
+
def remove_all_managed!
|
|
248
|
+
content = read
|
|
249
|
+
newc = strip_managed_blocks(content)
|
|
250
|
+
removed = (newc != content)
|
|
251
|
+
write(newc) if removed
|
|
252
|
+
|
|
253
|
+
UiHelpers.say(@ui, removed ?
|
|
254
|
+
"#{UiHelpers.e(:broom)} " + ::I18n.t("vdhm.messages.cleaned_all", default: "All managed hosts blocks removed.") :
|
|
255
|
+
"#{UiHelpers.e(:info)} " + ::I18n.t("vdhm.messages.nothing_to_clean", default: "Nothing to clean."))
|
|
212
256
|
removed
|
|
213
257
|
end
|
|
214
258
|
|
|
259
|
+
def strip_managed_blocks(content)
|
|
260
|
+
removing = false
|
|
261
|
+
content.lines.reject do |line|
|
|
262
|
+
if line.match?(BLOCK_START_RE)
|
|
263
|
+
removing = true
|
|
264
|
+
true
|
|
265
|
+
elsif line.match?(BLOCK_STOP_RE)
|
|
266
|
+
removing = false
|
|
267
|
+
true
|
|
268
|
+
else
|
|
269
|
+
removing
|
|
270
|
+
end
|
|
271
|
+
end.join
|
|
272
|
+
end
|
|
273
|
+
|
|
215
274
|
def read
|
|
275
|
+
# Hosts files are often edited by Windows tools with BOMs or legacy
|
|
276
|
+
# encodings; normalize to UTF-8 before parsing managed blocks.
|
|
216
277
|
pth = real_path
|
|
217
278
|
UiHelpers.debug(@ui, "read(#{pth})")
|
|
218
279
|
|
|
@@ -229,6 +290,7 @@ module VagrantDockerHostsManager
|
|
|
229
290
|
if (data.nil? || data.empty?) && Gem.win_platform?
|
|
230
291
|
ps_path = pth.gsub("'", "''")
|
|
231
292
|
ps_cmd = "Get-Content -LiteralPath '#{ps_path}' -Raw"
|
|
293
|
+
Verbose.log("powershell -Command #{ps_cmd}")
|
|
232
294
|
out, err, st = Open3.capture3("powershell", "-NoProfile", "-NonInteractive", "-Command", ps_cmd)
|
|
233
295
|
if st.success?
|
|
234
296
|
data = out
|
|
@@ -275,29 +337,28 @@ module VagrantDockerHostsManager
|
|
|
275
337
|
""
|
|
276
338
|
end
|
|
277
339
|
|
|
278
|
-
def
|
|
279
|
-
|
|
280
|
-
|
|
340
|
+
def each_managed_entry(scope = :all)
|
|
341
|
+
# A single scanner powers both current-owner and all-owner cleanup paths
|
|
342
|
+
# so marker parsing rules stay identical.
|
|
343
|
+
return enum_for(:each_managed_entry, scope) unless block_given?
|
|
281
344
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
ip_re = /^\s*(\d{1,3}(?:\.\d{1,3}){3})\s+([^\s#]+)/
|
|
345
|
+
content = read
|
|
346
|
+
return if content.to_s.empty?
|
|
285
347
|
|
|
286
|
-
out = []
|
|
287
348
|
in_block = false
|
|
288
349
|
owner = nil
|
|
289
350
|
|
|
290
351
|
content.each_line.with_index(1) do |raw, idx|
|
|
291
352
|
line = raw.delete_suffix("\n").delete_suffix("\r")
|
|
292
353
|
|
|
293
|
-
if (m = line.match(
|
|
354
|
+
if (m = line.match(BLOCK_START_RE))
|
|
294
355
|
in_block = true
|
|
295
356
|
owner = m[1].to_s.strip
|
|
296
357
|
UiHelpers.debug(@ui, "start block(owner=#{owner}) at line #{idx}")
|
|
297
358
|
next
|
|
298
359
|
end
|
|
299
360
|
|
|
300
|
-
if
|
|
361
|
+
if line.match?(BLOCK_STOP_RE)
|
|
301
362
|
UiHelpers.debug(@ui, "stop block(owner=#{owner}) at line #{idx}") if in_block
|
|
302
363
|
in_block = false
|
|
303
364
|
owner = nil
|
|
@@ -307,103 +368,25 @@ module VagrantDockerHostsManager
|
|
|
307
368
|
next unless in_block
|
|
308
369
|
next unless scope == :all || owner == @owner_id
|
|
309
370
|
|
|
310
|
-
if (m = line.match(
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
UiHelpers.debug(@ui, " ip line: #{ip} #{fqdn} (owner=#{owner})")
|
|
371
|
+
if (m = line.match(ENTRY_RE))
|
|
372
|
+
UiHelpers.debug(@ui, " ip line: #{m[1]} #{m[2]} (owner=#{owner})")
|
|
373
|
+
yield m[1], m[2], owner
|
|
314
374
|
end
|
|
315
375
|
end
|
|
316
|
-
|
|
317
|
-
UiHelpers.debug(@ui, "list_pairs found #{out.length} pair(s)")
|
|
318
|
-
out
|
|
319
376
|
end
|
|
320
377
|
|
|
321
|
-
def
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
stop_prefix = "# <<< vagrant-docker-hosts-manager "
|
|
327
|
-
|
|
328
|
-
in_block = false
|
|
329
|
-
owner = nil
|
|
330
|
-
out = {}
|
|
331
|
-
|
|
332
|
-
content.each_line do |raw|
|
|
333
|
-
line = raw.sub(/\r?\n\z/, "")
|
|
334
|
-
lstr = line.lstrip
|
|
335
|
-
|
|
336
|
-
if lstr.start_with?(start_prefix)
|
|
337
|
-
in_block = true
|
|
338
|
-
tail = lstr[start_prefix.length..-1].to_s
|
|
339
|
-
owner = tail.split(" (managed)").first.to_s.strip
|
|
340
|
-
next
|
|
341
|
-
end
|
|
342
|
-
|
|
343
|
-
if lstr.start_with?(stop_prefix)
|
|
344
|
-
in_block = false
|
|
345
|
-
owner = nil
|
|
346
|
-
next
|
|
347
|
-
end
|
|
348
|
-
|
|
349
|
-
next unless in_block
|
|
350
|
-
next unless scope == :all || owner == @owner_id
|
|
351
|
-
|
|
352
|
-
if lstr =~ /\A\s*(\d{1,3}(?:\.\d{1,3}){3})\s+([^\s#]+)/
|
|
353
|
-
ip, fqdn = $1, $2
|
|
354
|
-
if out.key?(fqdn)
|
|
355
|
-
arr = out[fqdn].is_a?(Array) ? out[fqdn] : [out[fqdn]]
|
|
356
|
-
arr << ip unless arr.include?(ip)
|
|
357
|
-
out[fqdn] = arr
|
|
358
|
-
else
|
|
359
|
-
out[fqdn] = [ip]
|
|
360
|
-
end
|
|
361
|
-
end
|
|
362
|
-
end
|
|
363
|
-
|
|
364
|
-
out
|
|
378
|
+
def list_pairs(scope = :all)
|
|
379
|
+
pairs = []
|
|
380
|
+
each_managed_entry(scope) { |ip, fqdn, owner| pairs << [ip, fqdn, owner] }
|
|
381
|
+
UiHelpers.debug(@ui, "list_pairs found #{pairs.length} pair(s)")
|
|
382
|
+
pairs
|
|
365
383
|
end
|
|
366
384
|
|
|
367
|
-
def
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
start_re = /^\s*#\s*>>>\s*vagrant-docker-hosts-manager\s+(.+?)\s*\(managed\)\s*>>>\s*$/i
|
|
372
|
-
stop_re = /^\s*#\s*<<<\s*vagrant-docker-hosts-manager\s+(.+?)\s*\(managed\)\s*<<<\s*$/i
|
|
373
|
-
|
|
374
|
-
buff = []
|
|
375
|
-
cur = []
|
|
376
|
-
in_block = false
|
|
377
|
-
owner = nil
|
|
378
|
-
|
|
379
|
-
content.each_line do |raw|
|
|
380
|
-
line = raw.delete_suffix("\n").delete_suffix("\r")
|
|
381
|
-
|
|
382
|
-
if (m = line.match(start_re))
|
|
383
|
-
in_block = true
|
|
384
|
-
owner = m[1].to_s.strip
|
|
385
|
-
cur = [line]
|
|
386
|
-
next
|
|
387
|
-
end
|
|
388
|
-
|
|
389
|
-
if in_block
|
|
390
|
-
cur << line
|
|
391
|
-
if line.match?(stop_re)
|
|
392
|
-
if scope == :all || owner == @owner_id
|
|
393
|
-
buff << cur.join("\n")
|
|
394
|
-
end
|
|
395
|
-
in_block = false
|
|
396
|
-
owner = nil
|
|
397
|
-
cur = []
|
|
398
|
-
end
|
|
399
|
-
end
|
|
385
|
+
def entries_in_blocks(scope = :current)
|
|
386
|
+
each_managed_entry(scope).with_object({}) do |(ip, fqdn, _owner), out|
|
|
387
|
+
arr = (out[fqdn] ||= [])
|
|
388
|
+
arr << ip unless arr.include?(ip)
|
|
400
389
|
end
|
|
401
|
-
|
|
402
|
-
buff.join("\n\n\n")
|
|
403
|
-
end
|
|
404
|
-
|
|
405
|
-
def current_entries
|
|
406
|
-
entries_in_blocks(:current)
|
|
407
390
|
end
|
|
408
391
|
|
|
409
392
|
def remove_block_from(content)
|
|
@@ -429,6 +412,7 @@ module VagrantDockerHostsManager
|
|
|
429
412
|
[Security.Principal.WindowsIdentity]::GetCurrent()
|
|
430
413
|
)).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
|
431
414
|
}.strip
|
|
415
|
+
Verbose.log("powershell -Command (check administrator role)")
|
|
432
416
|
out, _err, st = Open3.capture3("powershell", "-NoProfile", "-NonInteractive", "-Command", cmd)
|
|
433
417
|
st.success? && out.to_s.strip.downcase == "true"
|
|
434
418
|
else
|
|
@@ -451,6 +435,7 @@ module VagrantDockerHostsManager
|
|
|
451
435
|
begin
|
|
452
436
|
tf.binmode
|
|
453
437
|
tf.write(content); tf.flush
|
|
438
|
+
Verbose.log("sudo", "cp", tf.path, real_path)
|
|
454
439
|
system("sudo", "cp", tf.path, real_path) || raise("sudo copy failed")
|
|
455
440
|
ensure
|
|
456
441
|
tf.close!
|
|
@@ -458,6 +443,8 @@ module VagrantDockerHostsManager
|
|
|
458
443
|
end
|
|
459
444
|
|
|
460
445
|
def write_windows(content)
|
|
446
|
+
# Try a direct write first; fall back to UAC only when the hosts file
|
|
447
|
+
# rejects it. Base64/UTF-16LE keeps PowerShell quoting predictable.
|
|
461
448
|
b64 = Base64.strict_encode64(
|
|
462
449
|
content.encode("UTF-8", invalid: :replace, undef: :replace, replace: "")
|
|
463
450
|
)
|
|
@@ -465,16 +452,31 @@ module VagrantDockerHostsManager
|
|
|
465
452
|
|
|
466
453
|
ps = <<~POW
|
|
467
454
|
$ErrorActionPreference = "Stop"
|
|
468
|
-
|
|
469
|
-
|
|
455
|
+
try {
|
|
456
|
+
$bytes = [System.Convert]::FromBase64String('#{b64}')
|
|
457
|
+
[System.IO.File]::WriteAllBytes('#{dest}', $bytes)
|
|
458
|
+
exit 0
|
|
459
|
+
} catch {
|
|
460
|
+
exit 1
|
|
461
|
+
}
|
|
470
462
|
POW
|
|
471
463
|
encoded = Base64.strict_encode64(ps.encode("UTF-16LE"))
|
|
464
|
+
|
|
465
|
+
Verbose.log("powershell -EncodedCommand (write hosts file: #{real_path})")
|
|
472
466
|
_out, _err, st = Open3.capture3("powershell", "-NoProfile", "-NonInteractive", "-EncodedCommand", encoded)
|
|
473
467
|
return if st.success?
|
|
474
468
|
|
|
475
|
-
elev_ps
|
|
476
|
-
|
|
469
|
+
elev_ps = <<~POW
|
|
470
|
+
$ErrorActionPreference = 'Stop'
|
|
471
|
+
try {
|
|
472
|
+
$p = Start-Process PowerShell -Verb RunAs -Wait -PassThru -ArgumentList '-NonInteractive','-NoProfile','-EncodedCommand','#{encoded}'
|
|
473
|
+
exit $p.ExitCode
|
|
474
|
+
} catch {
|
|
475
|
+
exit 1
|
|
476
|
+
}
|
|
477
|
+
POW
|
|
477
478
|
elev_encoded = Base64.strict_encode64(elev_ps.encode("UTF-16LE"))
|
|
479
|
+
Verbose.log("powershell -EncodedCommand (elevated write hosts file via RunAs: #{real_path})")
|
|
478
480
|
system("powershell", "-NoProfile", "-NonInteractive", "-EncodedCommand", elev_encoded) ||
|
|
479
481
|
raise("elevated write failed")
|
|
480
482
|
end
|
|
@@ -33,7 +33,7 @@ module VagrantDockerHostsManager
|
|
|
33
33
|
begin
|
|
34
34
|
if env[:ui] && env[:machine]&.config&.docker_hosts&.respond_to?(:verbose) &&
|
|
35
35
|
env[:machine].config.docker_hosts.verbose
|
|
36
|
-
env[:ui].info(::I18n.t("messages.lang_set", lang: ::I18n.locale))
|
|
36
|
+
env[:ui].info(::I18n.t("vdhm.messages.lang_set", lang: ::I18n.locale))
|
|
37
37
|
end
|
|
38
38
|
rescue StandardError
|
|
39
39
|
nil
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "shellwords"
|
|
4
|
+
|
|
5
|
+
module VagrantDockerHostsManager
|
|
6
|
+
module Util
|
|
7
|
+
module Verbose
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def enabled?
|
|
11
|
+
ENV["VDHM_VERBOSE"].to_s == "1"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def log(*args)
|
|
15
|
+
return unless enabled?
|
|
16
|
+
|
|
17
|
+
line = args.length == 1 && args.first.is_a?(String) ? args.first : args.map(&:to_s).shelljoin
|
|
18
|
+
warn("[VDHM] #{line}")
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|