vagrant-docker-hosts-manager 0.2.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 +8 -0
- data/LICENSE.md +22 -0
- data/README.md +196 -0
- data/lib/vagrant-docker-hosts-manager/VERSION +1 -0
- data/lib/vagrant-docker-hosts-manager/command.rb +457 -0
- data/lib/vagrant-docker-hosts-manager/config.rb +44 -0
- data/lib/vagrant-docker-hosts-manager/helpers.rb +42 -0
- data/lib/vagrant-docker-hosts-manager/plugin.rb +142 -0
- data/lib/vagrant-docker-hosts-manager/util/docker.rb +29 -0
- data/lib/vagrant-docker-hosts-manager/util/hosts_file.rb +477 -0
- data/lib/vagrant-docker-hosts-manager/util/i18n.rb +40 -0
- data/lib/vagrant-docker-hosts-manager/util/json.rb +15 -0
- data/lib/vagrant-docker-hosts-manager/version.rb +10 -0
- data/lib/vagrant-docker-hosts-manager.rb +3 -0
- data/locales/en.yml +114 -0
- data/locales/fr.yml +115 -0
- metadata +108 -0
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module VagrantDockerHostsManager
|
4
|
+
class Config < Vagrant.plugin("2", :config)
|
5
|
+
attr_accessor :domains
|
6
|
+
|
7
|
+
attr_accessor :domain
|
8
|
+
|
9
|
+
attr_accessor :container_name
|
10
|
+
|
11
|
+
attr_accessor :ip
|
12
|
+
|
13
|
+
attr_accessor :verbose
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@domains = {}
|
17
|
+
@domain = nil
|
18
|
+
@container_name = nil
|
19
|
+
@ip = nil
|
20
|
+
@verbose = false
|
21
|
+
end
|
22
|
+
|
23
|
+
def finalize!; end
|
24
|
+
|
25
|
+
def validate(_machine)
|
26
|
+
errors = []
|
27
|
+
if (@domains.nil? || @domains.empty?) && (@domain.nil? || @domain.strip.empty?)
|
28
|
+
errors << "You must configure at least one domain: " \
|
29
|
+
"`config.docker_hosts.domain = \"example.test\"` or set " \
|
30
|
+
"`config.docker_hosts.domains = {\"example.test\" => \"172.28.0.10\"}`"
|
31
|
+
end
|
32
|
+
|
33
|
+
unless @domains.is_a?(Hash)
|
34
|
+
errors << "`domains` must be a Hash of { \\\"domain\\\" => \\\"ip\\\" }"
|
35
|
+
end
|
36
|
+
|
37
|
+
if @ip && !@ip.to_s.match?(/\A\d{1,3}(\.\d{1,3}){3}\z/)
|
38
|
+
errors << "`ip` must be IPv4 like 172.28.0.10"
|
39
|
+
end
|
40
|
+
|
41
|
+
{ "vagrant-docker-hosts-manager" => errors }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "i18n"
|
4
|
+
|
5
|
+
module VagrantDockerHostsManager
|
6
|
+
module UiHelpers
|
7
|
+
EMOJI = {
|
8
|
+
success: "โ
",
|
9
|
+
info: "๐",
|
10
|
+
ongoing: "๐",
|
11
|
+
warning: "โ ๏ธ",
|
12
|
+
error: "โ",
|
13
|
+
version: "๐พ",
|
14
|
+
broom: "๐งน",
|
15
|
+
question: "โ",
|
16
|
+
bug: "๐"
|
17
|
+
}.freeze
|
18
|
+
|
19
|
+
module_function
|
20
|
+
|
21
|
+
def e(key, no_emoji: false)
|
22
|
+
return "" if no_emoji || ENV["VDHM_NO_EMOJI"] == "1"
|
23
|
+
EMOJI[key] || ""
|
24
|
+
end
|
25
|
+
|
26
|
+
def debug_enabled?
|
27
|
+
ENV["VDHM_DEBUG"].to_s == "1"
|
28
|
+
end
|
29
|
+
|
30
|
+
def t(key, **opts) = ::I18n.t(key, **opts)
|
31
|
+
def exists?(key) = ::I18n.exists?(key, ::I18n.locale)
|
32
|
+
|
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))
|
36
|
+
|
37
|
+
def debug(ui, msg)
|
38
|
+
return unless debug_enabled?
|
39
|
+
say(ui, "#{e(:bug)} #{msg}")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "vagrant"
|
4
|
+
require "i18n"
|
5
|
+
|
6
|
+
require_relative "version"
|
7
|
+
require_relative "config"
|
8
|
+
require_relative "command"
|
9
|
+
require_relative "helpers"
|
10
|
+
require_relative "util/hosts_file"
|
11
|
+
require_relative "util/docker"
|
12
|
+
require_relative "util/json"
|
13
|
+
require_relative "util/i18n"
|
14
|
+
|
15
|
+
begin
|
16
|
+
I18n.enforce_available_locales = false
|
17
|
+
gem_root = File.expand_path("../..", __dir__)
|
18
|
+
I18n.load_path |= Dir[File.join(gem_root, "locales", "*.yml")]
|
19
|
+
I18n.backend.load_translations
|
20
|
+
rescue StandardError
|
21
|
+
end
|
22
|
+
|
23
|
+
module VagrantDockerHostsManager
|
24
|
+
class Plugin < Vagrant.plugin("2")
|
25
|
+
name "vagrant-docker-hosts-manager"
|
26
|
+
|
27
|
+
description <<~DESC
|
28
|
+
Manage /etc/hosts (or Windows hosts) entries for Docker/Vagrant projects safely,
|
29
|
+
with ownership markers, CLI, JSON output, and lifecycle hooks.
|
30
|
+
DESC
|
31
|
+
|
32
|
+
config(:docker_hosts) do
|
33
|
+
Config
|
34
|
+
end
|
35
|
+
|
36
|
+
command("hosts") do
|
37
|
+
Command
|
38
|
+
end
|
39
|
+
|
40
|
+
[:machine_action_up, :machine_action_provision, :machine_action_reload].each do |hook_name|
|
41
|
+
action_hook(:vdhm_apply, hook_name) do |hook|
|
42
|
+
hook.after(Vagrant::Action::Builtin::Provision, Action::Apply)
|
43
|
+
hook.append(Action::Apply)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
action_hook(:vdhm_cleanup, :machine_action_destroy) do |hook|
|
48
|
+
hook.prepend(Action::Cleanup)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
module Action
|
53
|
+
class Apply
|
54
|
+
def initialize(app, env) = (@app = app)
|
55
|
+
|
56
|
+
def call(env)
|
57
|
+
Util::I18n.setup!(env)
|
58
|
+
cfg = env[:machine].config.docker_hosts
|
59
|
+
mid = env[:machine].id || "unknown"
|
60
|
+
dry = Util::I18n.env_flag("VDHM_DRY_RUN")
|
61
|
+
ui = env[:ui]
|
62
|
+
hoster = Util::HostsFile.new(env, owner_id: mid)
|
63
|
+
|
64
|
+
entries = compute_entries(env, cfg, ui)
|
65
|
+
if entries.empty?
|
66
|
+
ui.info(::I18n.t("messages.no_entries"))
|
67
|
+
return @app.call(env)
|
68
|
+
end
|
69
|
+
|
70
|
+
if dry
|
71
|
+
Util::Json.emit(action: "apply", status: "dry-run", data: { owner: mid, entries: entries })
|
72
|
+
return @app.call(env)
|
73
|
+
end
|
74
|
+
|
75
|
+
hoster.apply(entries)
|
76
|
+
Util::Json.emit(action: "apply", status: "success", data: { owner: mid, entries: entries })
|
77
|
+
rescue => e
|
78
|
+
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}")
|
80
|
+
ensure
|
81
|
+
@app.call(env)
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def compute_entries(env, cfg, ui)
|
87
|
+
entries = {}
|
88
|
+
|
89
|
+
cfg.domains.each do |domain, ip|
|
90
|
+
next if domain.to_s.strip.empty?
|
91
|
+
if ip.nil? || ip.to_s.strip.empty?
|
92
|
+
ui&.warn(::I18n.t("messages.missing_ip_for", domain: domain))
|
93
|
+
next
|
94
|
+
end
|
95
|
+
entries[domain] = ip
|
96
|
+
end
|
97
|
+
|
98
|
+
if cfg.domain && !cfg.domain.strip.empty?
|
99
|
+
ip = cfg.ip || begin
|
100
|
+
if cfg.container_name && !cfg.container_name.strip.empty?
|
101
|
+
Util::Docker.ip_for_container(cfg.container_name)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
if ip && !ip.strip.empty?
|
105
|
+
ui&.info(::I18n.t("messages.detected_ip", domain: cfg.domain, ip: ip))
|
106
|
+
entries[cfg.domain] = ip
|
107
|
+
else
|
108
|
+
ui&.warn(::I18n.t("messages.no_ip_found", domain: cfg.domain, container: cfg.container_name))
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
entries
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
class Cleanup
|
117
|
+
def initialize(app, env) = (@app = app)
|
118
|
+
|
119
|
+
def call(env)
|
120
|
+
Util::I18n.setup!(env)
|
121
|
+
mid = env[:machine].id || "unknown"
|
122
|
+
dry = Util::I18n.env_flag("VDHM_DRY_RUN")
|
123
|
+
ui = env[:ui]
|
124
|
+
hoster = Util::HostsFile.new(env, owner_id: mid)
|
125
|
+
|
126
|
+
if dry
|
127
|
+
Util::Json.emit(action: "cleanup", status: "dry-run", data: { owner: mid })
|
128
|
+
return @app.call(env)
|
129
|
+
end
|
130
|
+
|
131
|
+
removed = hoster.remove!
|
132
|
+
Util::Json.emit(action: "cleanup", status: "success", data: { owner: mid, removed: removed })
|
133
|
+
ui.info(::I18n.t("messages.cleaned"))
|
134
|
+
rescue => e
|
135
|
+
Util::Json.emit(action: "cleanup", status: "error", error: e.message)
|
136
|
+
ui.error("VDHM: #{e.message}")
|
137
|
+
ensure
|
138
|
+
@app.call(env)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "open3"
|
4
|
+
|
5
|
+
module VagrantDockerHostsManager
|
6
|
+
module Util
|
7
|
+
module Docker
|
8
|
+
module_function
|
9
|
+
|
10
|
+
def ip_for_container(name)
|
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)
|
14
|
+
return nil unless status.success?
|
15
|
+
out.split(/\s+/).find { |ip| ip =~ /\A\d{1,3}(\.\d{1,3}){3}\z/ }
|
16
|
+
rescue
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def shell_escape(str)
|
21
|
+
if Gem.win_platform?
|
22
|
+
%("#{str.gsub('"', '\"')}")
|
23
|
+
else
|
24
|
+
%('#{str.gsub("'", "'\\\\''")}')
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|