vagrant-docker-hosts-manager 0.2.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 90a6a5b049e33dce80821b66d57d4c4ca4edd35d7e51f1125d96f4211adb3eb1
4
- data.tar.gz: 634910fe60c21185160b1269bdc6b3cbb77e0d6e6e7df8d5a2cb6eb6115762e1
3
+ metadata.gz: 66272a00d09743fc1c067456c1680ac75768ca3d3c9534f1ad01eac5bf3b139e
4
+ data.tar.gz: 24452535a62c76a094e86d77398697647207528e9cea5315b53b8031822ebfb5
5
5
  SHA512:
6
- metadata.gz: a1523d6ca1643bac26e2040020b9207a62d6e1b1328b89f56eae7e6d33ad69e6f2c136cee1c2c8e4824388ec126be962ad898107ab08cac66aa310f1a33f7c7b
7
- data.tar.gz: da1215b0b930905713b6d9e202035e6523dd1878d9fbdf535a55bbfbc6706200a006c8bc2935ea4c2213efaed23584d7a98a6d5ce31fe7c168cf59ff5e01471a
6
+ metadata.gz: 2ba755242cce735714503633ef65d425e9368fc0871bf85a268da447b32c3d7032610db4994707de04cf64df79ff94ab587815ddeb11ad3e3571664b7680b200
7
+ data.tar.gz: d2e14700ece77c25e67c59b5a0c033f3daec0b86cea3227ff0438052163b9c577e39e8bb482ec0ef3e8f5e34a3a44471143dff13a45656f9884454f7bcc3f2d7
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.0](https://github.com/julienpoirou/vagrant-docker-hosts-manager/compare/v0.2.0...v0.3.0) (2026-03-11)
4
+
5
+
6
+ ### Fonctionnalités ✨
7
+
8
+ * **i18n:** Add locale config attr and centralize i18n setup in UiHelpers ([9336f87](https://github.com/julienpoirou/vagrant-docker-hosts-manager/commit/9336f878cebcfa98cfc615fd206014cf2cebe47a))
9
+
3
10
  ## [0.2.0](https://github.com/julienpoirou/vagrant-docker-hosts-manager/compare/v0.1.0...v0.2.0) (2025-08-20)
4
11
 
5
12
 
data/README.md CHANGED
@@ -7,6 +7,7 @@
7
7
  [![License](https://img.shields.io/github/license/julienpoirou/vagrant-docker-hosts-manager.svg)](LICENSE.md)
8
8
  [![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-%23FE5196.svg)](https://www.conventionalcommits.org)
9
9
  [![Renovate](https://img.shields.io/badge/Renovate-enabled-brightgreen.svg)](https://renovatebot.com)
10
+ [![Total downloads](https://img.shields.io/gem/dt/vagrant-docker-hosts-manager?logo=rubygems&label=downloads)](https://rubygems.org/gems/vagrant-docker-hosts-manager)
10
11
 
11
12
  Vagrant plugin to **manage custom hostnames** by updating the local `hosts` file (Windows/Linux/macOS) for Docker‑based environments.
12
13
 
@@ -1 +1 @@
1
- 0.2.0
1
+ 0.3.0
@@ -18,7 +18,7 @@ module VagrantDockerHostsManager
18
18
  i = raw_argv.index { |x| x =~ /\A(?:help|--help|-h)\z/ }
19
19
  cand = (i && raw_argv[i + 1]) ? raw_argv[i + 1] : nil
20
20
  (cand && cand !~ /\A-/) ? cand : nil
21
- rescue
21
+ rescue StandardError
22
22
  nil
23
23
  end
24
24
 
@@ -274,7 +274,7 @@ module VagrantDockerHostsManager
274
274
  print_help(@env.ui, no_emoji: opts[:no_emoji])
275
275
  return 0
276
276
  end
277
- rescue => e
277
+ rescue StandardError => e
278
278
  Util::Json.emit(action: "command", status: "error", error: e.message)
279
279
  1
280
280
  end
@@ -294,7 +294,7 @@ module VagrantDockerHostsManager
294
294
  $stdout.print(line)
295
295
  $stdout.flush
296
296
  answer = ($stdin.gets || "").to_s
297
- rescue
297
+ rescue StandardError
298
298
  answer = ""
299
299
  ensure
300
300
  $stdout.puts ""
@@ -3,13 +3,10 @@
3
3
  module VagrantDockerHostsManager
4
4
  class Config < Vagrant.plugin("2", :config)
5
5
  attr_accessor :domains
6
-
7
6
  attr_accessor :domain
8
-
9
7
  attr_accessor :container_name
10
-
11
8
  attr_accessor :ip
12
-
9
+ attr_accessor :locale
13
10
  attr_accessor :verbose
14
11
 
15
12
  def initialize
@@ -17,6 +14,7 @@ module VagrantDockerHostsManager
17
14
  @domain = nil
18
15
  @container_name = nil
19
16
  @ip = nil
17
+ @locale = nil
20
18
  @verbose = false
21
19
  end
22
20
 
@@ -24,6 +22,7 @@ module VagrantDockerHostsManager
24
22
 
25
23
  def validate(_machine)
26
24
  errors = []
25
+
27
26
  if (@domains.nil? || @domains.empty?) && (@domain.nil? || @domain.strip.empty?)
28
27
  errors << "You must configure at least one domain: " \
29
28
  "`config.docker_hosts.domain = \"example.test\"` or set " \
@@ -38,6 +37,10 @@ module VagrantDockerHostsManager
38
37
  errors << "`ip` must be IPv4 like 172.28.0.10"
39
38
  end
40
39
 
40
+ if @locale && !%w[en fr].include?(@locale.to_s[0, 2].downcase)
41
+ errors << "`locale` must be 'en' or 'fr'."
42
+ end
43
+
41
44
  { "vagrant-docker-hosts-manager" => errors }
42
45
  end
43
46
  end
@@ -4,6 +4,12 @@ require "i18n"
4
4
 
5
5
  module VagrantDockerHostsManager
6
6
  module UiHelpers
7
+ class MissingTranslationError < StandardError; end
8
+ class UnsupportedLocaleError < StandardError; end
9
+
10
+ SUPPORTED = [:en, :fr].freeze
11
+ OUR_NAMESPACES = %w[messages. errors. log. help.].freeze
12
+
7
13
  EMOJI = {
8
14
  success: "✅",
9
15
  info: "🔍",
@@ -18,21 +24,89 @@ module VagrantDockerHostsManager
18
24
 
19
25
  module_function
20
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["VDHM_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 setup_locale_from_config!(cfg)
56
+ lang = (cfg.respond_to?(:locale) ? cfg.locale : nil) || ENV["VDHM_LANG"]
57
+ return unless lang
58
+ set_locale!(lang)
59
+ rescue UnsupportedLocaleError
60
+ set_locale!("en")
61
+ end
62
+
63
+ def t(key, **opts)
64
+ ::I18n.t(key, **opts)
65
+ end
66
+
67
+ def t!(key, **opts)
68
+ setup_i18n!
69
+ k = key.to_s
70
+ if our_key?(k) && !::I18n.exists?(k, ::I18n.locale)
71
+ raise MissingTranslationError, "#{EMOJI[:error]} [#{::I18n.locale}] Missing translation for key: #{k}"
72
+ end
73
+ ::I18n.t(k, **opts)
74
+ end
75
+
76
+ def t_hash(key)
77
+ setup_i18n!
78
+ v = ::I18n.t(key, default: {})
79
+ v.is_a?(Hash) ? v : {}
80
+ end
81
+
82
+ def exists?(key)
83
+ ::I18n.exists?(key, ::I18n.locale)
84
+ end
85
+
86
+ def our_key?(k)
87
+ OUR_NAMESPACES.any? { |ns| k.start_with?(ns) }
88
+ end
89
+
21
90
  def e(key, no_emoji: false)
22
91
  return "" if no_emoji || ENV["VDHM_NO_EMOJI"] == "1"
23
92
  EMOJI[key] || ""
24
93
  end
25
94
 
26
- def debug_enabled?
27
- ENV["VDHM_DEBUG"].to_s == "1"
95
+ def say(ui, msg)
96
+ ui&.info(msg) || puts(msg)
97
+ end
98
+
99
+ def warn(ui, msg)
100
+ ui&.warn(msg) || puts(msg)
28
101
  end
29
102
 
30
- def t(key, **opts) = ::I18n.t(key, **opts)
31
- def exists?(key) = ::I18n.exists?(key, ::I18n.locale)
103
+ def error(ui, msg)
104
+ ui&.error(msg) || warn(ui, msg)
105
+ end
32
106
 
33
- def say(ui, msg) = (ui&.info(msg) || puts(msg))
34
- def warn(ui, msg) = (ui&.warn(msg) || puts(msg))
35
- def error(ui, msg) = (ui&.error(msg) || warn(ui, msg))
107
+ def debug_enabled?
108
+ ENV["VDHM_DEBUG"].to_s == "1"
109
+ end
36
110
 
37
111
  def debug(ui, msg)
38
112
  return unless debug_enabled?
@@ -39,7 +39,6 @@ module VagrantDockerHostsManager
39
39
 
40
40
  [:machine_action_up, :machine_action_provision, :machine_action_reload].each do |hook_name|
41
41
  action_hook(:vdhm_apply, hook_name) do |hook|
42
- hook.after(Vagrant::Action::Builtin::Provision, Action::Apply)
43
42
  hook.append(Action::Apply)
44
43
  end
45
44
  end
@@ -63,20 +62,20 @@ module VagrantDockerHostsManager
63
62
 
64
63
  entries = compute_entries(env, cfg, ui)
65
64
  if entries.empty?
66
- ui.info(::I18n.t("messages.no_entries"))
67
- return @app.call(env)
65
+ UiHelpers.say(ui, ::I18n.t("messages.no_entries"))
66
+ return
68
67
  end
69
68
 
70
69
  if dry
71
70
  Util::Json.emit(action: "apply", status: "dry-run", data: { owner: mid, entries: entries })
72
- return @app.call(env)
71
+ return
73
72
  end
74
73
 
75
74
  hoster.apply(entries)
76
75
  Util::Json.emit(action: "apply", status: "success", data: { owner: mid, entries: entries })
77
- rescue => e
76
+ rescue StandardError => e
78
77
  Util::Json.emit(action: "apply", status: "error", error: e.message, backtrace: e.backtrace&.first(3))
79
- ui&.error("VDHM: #{e.message}") || puts("VDHM: #{e.message}")
78
+ UiHelpers.error(ui, "VDHM: #{e.message}")
80
79
  ensure
81
80
  @app.call(env)
82
81
  end
@@ -89,7 +88,7 @@ module VagrantDockerHostsManager
89
88
  cfg.domains.each do |domain, ip|
90
89
  next if domain.to_s.strip.empty?
91
90
  if ip.nil? || ip.to_s.strip.empty?
92
- ui&.warn(::I18n.t("messages.missing_ip_for", domain: domain))
91
+ UiHelpers.warn(ui, ::I18n.t("messages.missing_ip_for", domain: domain))
93
92
  next
94
93
  end
95
94
  entries[domain] = ip
@@ -102,10 +101,10 @@ module VagrantDockerHostsManager
102
101
  end
103
102
  end
104
103
  if ip && !ip.strip.empty?
105
- ui&.info(::I18n.t("messages.detected_ip", domain: cfg.domain, ip: ip))
104
+ UiHelpers.say(ui, ::I18n.t("messages.detected_ip", domain: cfg.domain, ip: ip))
106
105
  entries[cfg.domain] = ip
107
106
  else
108
- ui&.warn(::I18n.t("messages.no_ip_found", domain: cfg.domain, container: cfg.container_name))
107
+ UiHelpers.warn(ui, ::I18n.t("messages.no_ip_found", domain: cfg.domain, container: cfg.container_name))
109
108
  end
110
109
  end
111
110
 
@@ -125,15 +124,14 @@ module VagrantDockerHostsManager
125
124
 
126
125
  if dry
127
126
  Util::Json.emit(action: "cleanup", status: "dry-run", data: { owner: mid })
128
- return @app.call(env)
127
+ return
129
128
  end
130
129
 
131
130
  removed = hoster.remove!
132
131
  Util::Json.emit(action: "cleanup", status: "success", data: { owner: mid, removed: removed })
133
- ui.info(::I18n.t("messages.cleaned"))
134
- rescue => e
132
+ rescue StandardError => e
135
133
  Util::Json.emit(action: "cleanup", status: "error", error: e.message)
136
- ui.error("VDHM: #{e.message}")
134
+ UiHelpers.error(ui, "VDHM: #{e.message}")
137
135
  ensure
138
136
  @app.call(env)
139
137
  end
@@ -9,19 +9,22 @@ module VagrantDockerHostsManager
9
9
 
10
10
  def ip_for_container(name)
11
11
  return nil if name.to_s.strip.empty?
12
- cmd = %(docker inspect -f "{{range .NetworkSettings.Networks}}{{.IPAddress}} {{end}}" #{shell_escape(name)})
13
- out, _err, status = Open3.capture3(cmd)
12
+ out, _err, status = Open3.capture3(
13
+ "docker", "inspect", "-f",
14
+ "{{range .NetworkSettings.Networks}}{{.IPAddress}} {{end}}",
15
+ name.to_s
16
+ )
14
17
  return nil unless status.success?
15
18
  out.split(/\s+/).find { |ip| ip =~ /\A\d{1,3}(\.\d{1,3}){3}\z/ }
16
- rescue
19
+ rescue StandardError
17
20
  nil
18
21
  end
19
22
 
20
23
  def shell_escape(str)
21
24
  if Gem.win_platform?
22
- %("#{str.gsub('"', '\"')}")
25
+ %('#{str.to_s.gsub("'", "''")}')
23
26
  else
24
- %('#{str.gsub("'", "'\\\\''")}')
27
+ %('#{str.to_s.gsub("'", "'\\''")}')
25
28
  end
26
29
  end
27
30
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "base64"
3
4
  require "tempfile"
4
5
  require "time"
5
6
  require "open3"
@@ -62,7 +63,11 @@ module VagrantDockerHostsManager
62
63
 
63
64
  def compose_block(entries, newline: "\n")
64
65
  start, stop = block_markers
65
- ts = (Time.now.utc.iso8601 rescue Time.now.utc.to_s)
66
+ ts = begin
67
+ Time.now.utc.iso8601
68
+ rescue StandardError
69
+ Time.now.utc.to_s
70
+ end
66
71
 
67
72
  header = [
68
73
  start,
@@ -216,7 +221,7 @@ module VagrantDockerHostsManager
216
221
  begin
217
222
  data = File.binread(pth)
218
223
  UiHelpers.debug(@ui, "File.binread ok, bytes=#{data.bytesize}, encoding=#{data.encoding}")
219
- rescue => e
224
+ rescue StandardError => e
220
225
  UiHelpers.debug(@ui, "File.binread error: #{e.class}: #{e.message}")
221
226
  data = nil
222
227
  end
@@ -253,7 +258,7 @@ module VagrantDockerHostsManager
253
258
  .force_encoding("Windows-1252")
254
259
  .encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "")
255
260
  UiHelpers.debug(@ui, "Transcoded via Windows-1252 -> UTF-8, encoding=#{data.encoding}")
256
- rescue => e2
261
+ rescue StandardError => e2
257
262
  UiHelpers.debug(@ui, "Windows-1252 fallback failed: #{e2.class}: #{e2.message}")
258
263
  data = data.force_encoding(Encoding::UTF_8)
259
264
  end
@@ -265,7 +270,7 @@ module VagrantDockerHostsManager
265
270
  end
266
271
 
267
272
  data
268
- rescue => e
273
+ rescue StandardError => e
269
274
  UiHelpers.debug(@ui, "read() fatal: #{e.class}: #{e.message}")
270
275
  ""
271
276
  end
@@ -429,7 +434,7 @@ module VagrantDockerHostsManager
429
434
  else
430
435
  begin
431
436
  Process.euid == 0
432
- rescue
437
+ rescue StandardError
433
438
  false
434
439
  end
435
440
  end
@@ -446,31 +451,32 @@ module VagrantDockerHostsManager
446
451
  begin
447
452
  tf.binmode
448
453
  tf.write(content); tf.flush
449
- system("sudo cp #{tf.path} #{real_path}") || raise("sudo copy failed")
454
+ system("sudo", "cp", tf.path, real_path) || raise("sudo copy failed")
450
455
  ensure
451
456
  tf.close!
452
457
  end
453
458
  end
454
459
 
455
460
  def write_windows(content)
456
- tf = Tempfile.new("vdhm-hosts")
457
- begin
458
- tf.binmode
459
- tf.write(content); tf.flush
460
- ps = <<~POW
461
- $ErrorActionPreference = "Stop"
462
- Copy-Item -LiteralPath '#{tf.path.gsub("'", "''")}' -Destination '#{real_path.gsub("'", "''")}' -Force
463
- POW
464
- system(%{powershell -NoProfile -NonInteractive -Command #{Util::Docker.shell_escape(ps)}}) ||
465
- system(
466
- %{
467
- powershell -NoProfile -NonInteractive -Command
468
- "Start-Process PowerShell -Verb RunAs -ArgumentList #{Util::Docker.shell_escape(ps)}"
469
- }.strip
470
- ) || raise("elevated copy failed")
471
- ensure
472
- tf.close!
473
- end
461
+ b64 = Base64.strict_encode64(
462
+ content.encode("UTF-8", invalid: :replace, undef: :replace, replace: "")
463
+ )
464
+ dest = real_path.gsub("'", "''")
465
+
466
+ ps = <<~POW
467
+ $ErrorActionPreference = "Stop"
468
+ $bytes = [System.Convert]::FromBase64String('#{b64}')
469
+ [System.IO.File]::WriteAllBytes('#{dest}', $bytes)
470
+ POW
471
+ encoded = Base64.strict_encode64(ps.encode("UTF-16LE"))
472
+ _out, _err, st = Open3.capture3("powershell", "-NoProfile", "-NonInteractive", "-EncodedCommand", encoded)
473
+ return if st.success?
474
+
475
+ elev_ps = "Start-Process PowerShell -Verb RunAs -Wait " \
476
+ "-ArgumentList '-NonInteractive', '-NoProfile', '-EncodedCommand', '#{encoded}'"
477
+ elev_encoded = Base64.strict_encode64(elev_ps.encode("UTF-16LE"))
478
+ system("powershell", "-NoProfile", "-NonInteractive", "-EncodedCommand", elev_encoded) ||
479
+ raise("elevated write failed")
474
480
  end
475
481
  end
476
482
  end
@@ -1,34 +1,42 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "i18n"
4
+ require_relative "../helpers"
4
5
 
5
6
  module VagrantDockerHostsManager
6
7
  module Util
7
8
  module I18n
8
- SUPPORTED = [:en, :fr].freeze
9
9
  @json = false
10
10
 
11
11
  module_function
12
12
 
13
13
  def setup!(env, forced: nil)
14
- ::I18n.enforce_available_locales = false
14
+ UiHelpers.setup_i18n!
15
15
 
16
- gem_root = File.expand_path("../..", __dir__)
17
- paths = Dir[File.join(gem_root, "locales", "*.yml")]
18
- ::I18n.load_path |= paths
19
- ::I18n.available_locales = SUPPORTED
20
- ::I18n.backend.load_translations
16
+ if forced
17
+ begin
18
+ UiHelpers.set_locale!(forced)
19
+ rescue UiHelpers::UnsupportedLocaleError
20
+ UiHelpers.set_locale!("en")
21
+ end
22
+ end
21
23
 
22
- lang = forced || ENV["VDHM_LANG"] || ENV["LANG"] || "en"
23
- sym = lang.to_s[0, 2].downcase.to_sym
24
- ::I18n.locale = SUPPORTED.include?(sym) ? sym : :en
24
+ begin
25
+ cfg = env[:machine]&.config&.docker_hosts
26
+ if cfg&.respond_to?(:locale) && !cfg.locale.to_s.strip.empty?
27
+ UiHelpers.setup_locale_from_config!(cfg)
28
+ end
29
+ rescue StandardError
30
+ nil
31
+ end
25
32
 
26
33
  begin
27
- if env[:ui] && env[:machine] && env[:machine].config.docker_hosts.respond_to?(:verbose) &&
34
+ if env[:ui] && env[:machine]&.config&.docker_hosts&.respond_to?(:verbose) &&
28
35
  env[:machine].config.docker_hosts.verbose
29
36
  env[:ui].info(::I18n.t("messages.lang_set", lang: ::I18n.locale))
30
37
  end
31
38
  rescue StandardError
39
+ nil
32
40
  end
33
41
  end
34
42
 
@@ -1,10 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module VagrantDockerHostsManager
4
- VERSION = begin
5
- path = File.expand_path("VERSION", __dir__)
6
- File.exist?(path) ? File.read(path).strip : "0.0.0"
7
- rescue
8
- "0.0.0"
4
+ unless defined?(VERSION)
5
+ VERSION = begin
6
+ path = File.expand_path("VERSION", __dir__)
7
+ File.exist?(path) ? File.read(path).strip : "0.0.0"
8
+ rescue StandardError
9
+ "0.0.0"
10
+ end
9
11
  end
10
12
  end
data/locales/en.yml CHANGED
@@ -109,6 +109,7 @@ en:
109
109
  errors:
110
110
  no_machine: "No target machine found. Run this inside a Vagrant project or pass a VM name."
111
111
  not_admin: "Administrator/root privileges required to modify hosts at %{path}."
112
+ invalid_locale: "Locale must be 'en' or 'fr'."
112
113
 
113
114
  log:
114
115
  version_line: "vagrant-docker-hosts-manager version %{version}"
data/locales/fr.yml CHANGED
@@ -110,6 +110,7 @@ fr:
110
110
  errors:
111
111
  no_machine: "Aucune machine cible trouvée. Lancez cette commande dans un projet Vagrant ou indiquez un nom de VM."
112
112
  not_admin: "Droits administrateur/root requis pour modifier le fichier hosts : %{path}."
113
+ invalid_locale: "Locale doit être en 'en' ou en 'fr'."
113
114
 
114
115
  log:
115
116
  version_line: "vagrant-docker-hosts-manager version %{version}"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vagrant-docker-hosts-manager
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julien Poirou
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-08-20 00:00:00.000000000 Z
11
+ date: 2026-03-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: i18n