vagrant-dns 2.2.3 → 2.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.
@@ -1,4 +1,4 @@
1
- require 'fileutils'
1
+ require "fileutils"
2
2
 
3
3
  module VagrantDNS
4
4
  class Configurator
@@ -20,117 +20,188 @@ module VagrantDNS
20
20
  unregister_patterns!
21
21
  end
22
22
 
23
- private
24
- def validate_tlds
25
- valid, err = VagrantDNS::Config.validate_tlds(vm)
26
- if !valid
27
- vm.ui.error(err)
28
- elsif err
29
- vm.ui.warn(err)
30
- end
31
- valid
23
+ private def validate_tlds
24
+ valid, err = VagrantDNS::Config.validate_tlds(vm)
25
+ if !valid
26
+ vm.ui.error(err)
27
+ elsif err
28
+ vm.ui.warn(err)
32
29
  end
30
+ valid
31
+ end
33
32
 
34
- def regenerate_resolvers!
35
- FileUtils.mkdir_p(resolver_folder)
33
+ private def regenerate_resolvers!
34
+ resolver_folder = self.resolver_folder
35
+ _proto, ip, port = VagrantDNS::Config.listen.first
36
+ tlds = register_tlds!
36
37
 
37
- port = VagrantDNS::Config.listen.first.last
38
- tlds = dns_options(vm)[:tlds]
38
+ resolver_files(ip, port, tlds) do |filename, contents|
39
+ File.write(File.join(resolver_folder, filename), contents)
40
+ end
41
+ end
39
42
 
40
- tlds.each do |tld|
41
- File.open(File.join(resolver_folder, tld), "w") do |f|
42
- f << resolver_file(port)
43
- end
43
+ private def register_tlds!
44
+ add_tlds = dns_options(vm)[:tlds]
45
+ VagrantDNS::TldRegistry
46
+ .new(tmp_path)
47
+ .transaction do |store|
48
+ store["tlds"] ||= []
49
+ store["tlds"] |= add_tlds
50
+ store["tlds"]
44
51
  end
52
+ end
53
+
54
+ private def register_patterns!
55
+ opts = dns_options(vm)
56
+
57
+ patterns = opts[:patterns] || default_patterns(opts)
58
+ if patterns.empty?
59
+ vm.ui.warn "[vagrant-dns] TLD but no host_name given. No patterns will be configured."
60
+ return
45
61
  end
46
62
 
47
- def register_patterns!
48
- opts = dns_options(vm)
63
+ ip = vm_ip(opts)
64
+ unless ip
65
+ vm.ui.detail "[vagrant-dns] No patterns will be configured."
66
+ return
67
+ end
49
68
 
50
- patterns = opts[:patterns] || default_patterns(opts)
51
- if patterns.empty?
52
- vm.ui.warn '[vagrant-dns] TLD but no host_name given. No patterns will be configured.'
53
- return
54
- end
69
+ registry = Registry.new(tmp_path)
70
+ registry.transaction do
71
+ patterns.each { |pattern| registry[pattern] = ip }
72
+ end
73
+ end
55
74
 
56
- network = opts[:networks].find do |nw|
57
- nw.first == :private_network && nw.last[:ip]
75
+ private def unregister_patterns!
76
+ opts = dns_options(vm)
77
+
78
+ patterns = opts[:patterns] || default_patterns(opts)
79
+ if patterns.empty?
80
+ vm.ui.warn "[vagrant-dns] TLD but no host_name given. No patterns will be removed."
81
+ return
82
+ end
83
+
84
+ registry = Registry.open(tmp_path)
85
+ return unless registry
86
+
87
+ registry.transaction do
88
+ unless registry.any?
89
+ vm.ui.warn "[vagrant-dns] Configuration missing or empty. No patterns will be removed."
90
+ registry.abort
58
91
  end
59
92
 
60
- unless network
61
- network = opts[:networks].find do |nw|
62
- nw.first == :public_network && nw.last[:ip]
93
+ patterns.each do |pattern|
94
+ if (ip = registry.delete(pattern))
95
+ vm.ui.info "[vagrant-dns] Removing pattern: #{pattern} for ip: #{ip}"
96
+ else
97
+ vm.ui.info "[vagrant-dns] Pattern: #{pattern} was not in config."
63
98
  end
64
99
  end
100
+ end
101
+ end
65
102
 
66
- unless network
67
- vm.ui.warn '[vagrant-dns] Could not find any static network IP. No patterns will be configured.'
68
- return
69
- end
103
+ private def dns_options(vm)
104
+ return @dns_options if @dns_options
70
105
 
71
- ip = network.last[:ip]
106
+ @dns_options = vm.config.dns.to_hash
107
+ @dns_options[:host_name] = vm.config.vm.hostname
108
+ @dns_options[:networks] = vm.config.vm.networks
109
+ @dns_options
110
+ end
72
111
 
73
- registry = Registry.new(tmp_path)
74
- registry.transaction do
75
- patterns.each do |pattern|
76
- registry[pattern] = ip
77
- end
78
- end
112
+ private def default_patterns(opts)
113
+ if opts[:host_name]
114
+ opts[:tlds].map { |tld| /^.*#{opts[:host_name]}.#{tld}$/ }
115
+ else
116
+ []
79
117
  end
118
+ end
80
119
 
81
- def unregister_patterns!
82
- opts = dns_options(vm)
120
+ private def vm_ip(opts)
121
+ user_ip = opts[:ip]
83
122
 
84
- patterns = opts[:patterns] || default_patterns(opts)
85
- if patterns.empty?
86
- vm.ui.warn '[vagrant-dns] TLD but no host_name given. No patterns will be removed.'
87
- return
88
- end
123
+ if !user_ip && dynamic_ip_network?(opts) || [:dynamic, :dhcp].include?(user_ip)
124
+ user_ip = DYNAMIC_VM_IP
125
+ end
126
+
127
+ ip =
128
+ case user_ip
129
+ when Proc
130
+ if vm.communicate.ready?
131
+ user_ip.call(vm, opts.dup.freeze)
132
+ else
133
+ vm.ui.info "[vagrant-dns] Postponing running user provided IP script until box has started."
134
+ return
135
+ end
136
+ when Symbol
137
+ _ip = static_vm_ip(user_ip, opts)
89
138
 
90
- registry = Registry.new(tmp_path)
91
- registry.transaction do
92
- unless registry.any?
93
- vm.ui.warn '[vagrant-dns] Configuration missing or empty. No patterns will be removed.'
94
- registry.abort
139
+ unless _ip
140
+ vm.ui.warn "[vagrant-dns] Could not find any static network IP in network type `#{user_ip}'."
141
+ return
95
142
  end
96
143
 
97
- patterns.each do |pattern|
98
- if (ip = registry.delete(pattern))
99
- vm.ui.info "[vagrant-dns] Removing pattern: #{pattern} for ip: #{ip}"
100
- else
101
- vm.ui.info "[vagrant-dns] Pattern: #{pattern} was not in config."
102
- end
144
+ _ip
145
+ else
146
+ _ip = static_vm_ip(:private_network, opts)
147
+ _ip ||= static_vm_ip(:public_network, opts)
148
+
149
+ unless _ip
150
+ vm.ui.warn "[vagrant-dns] Could not find any static network IP."
151
+ return
103
152
  end
153
+
154
+ _ip
104
155
  end
156
+
157
+ # we where unable to find an IP, and there's no user-supplied callback
158
+ # falling back to dynamic/dhcp style detection
159
+ if !ip && !user_ip
160
+ vm.ui.info "[vagrant-dns] Falling back to dynamic IP detection."
161
+ ip = DYNAMIC_VM_IP.call(vm, opts.dup.freeze)
105
162
  end
106
163
 
107
- def dns_options(vm)
108
- dns_options = vm.config.dns.to_hash
109
- dns_options[:host_name] = vm.config.vm.hostname
110
- dns_options[:networks] = vm.config.vm.networks
111
- dns_options
164
+ if !ip || ip.empty?
165
+ vm.ui.warn "[vagrant-dns] Failed to identify IP."
166
+ return
112
167
  end
113
168
 
114
- def default_patterns(opts)
115
- if opts[:host_name]
116
- opts[:tlds].map { |tld| /^.*#{opts[:host_name]}.#{tld}$/ }
169
+ ip
170
+ end
171
+
172
+ private def dynamic_ip_network?(opts)
173
+ opts[:networks].none? { |(_nw_type, nw_config)| nw_config[:ip] }
174
+ end
175
+
176
+ # tries to find an IP in the configured +type+ networks
177
+ private def static_vm_ip(type, opts)
178
+ network = opts[:networks].find { |(nw_type, nw_config)| nw_config[:ip] && nw_type == type }
179
+
180
+ network.last[:ip] if network
181
+ end
182
+
183
+ DYNAMIC_VM_IP = proc { |vm|
184
+ vm.guest.capability(:read_ip_address).tap { |ip|
185
+ if ip
186
+ vm.ui.info "[vagrant-dns] Identified DHCP IP as '#{ip}'."
117
187
  else
118
- []
188
+ vm.ui.warn "[vagrant-dns] Could not identify DHCP IP."
119
189
  end
120
- end
190
+ }
191
+ }
192
+ private_constant :DYNAMIC_VM_IP
121
193
 
122
- def resolver_file(port)
123
- "# this file is generated by vagrant-dns\n" \
124
- "nameserver 127.0.0.1\n" \
125
- "port #{port}\n"
126
- end
194
+ private def resolver_files(ip, port, tlds, &block)
195
+ installer_class = VagrantDNS::Installers.resolve
196
+ installer_class.resolver_files(ip, port, tlds, &block)
197
+ end
127
198
 
128
- def resolver_folder
129
- File.join(tmp_path, "resolver")
130
- end
199
+ private def resolver_folder
200
+ File.join(tmp_path, "resolver").tap { |dir| FileUtils.mkdir_p(dir) }
201
+ end
131
202
 
132
- def ensure_deamon_env!
133
- FileUtils.mkdir_p(File.join(tmp_path, "daemon"))
134
- end
203
+ private def ensure_deamon_env!
204
+ FileUtils.mkdir_p(File.join(tmp_path, "daemon"))
205
+ end
135
206
  end
136
207
  end
@@ -0,0 +1,73 @@
1
+ module VagrantDNS
2
+ module Installers
3
+ class Linux
4
+ EXEC_STYLES = %i{sudo}
5
+ RESOLVED_CONFIG = "vagrant-dns.conf"
6
+
7
+ attr_accessor :tmp_path, :install_path, :exec_style
8
+
9
+ def initialize(tmp_path, options = {})
10
+ self.tmp_path = tmp_path
11
+ self.install_path = options.fetch(:install_path, "/etc/systemd/resolved.conf.d")
12
+ self.exec_style = options.fetch(:exec_style, :sudo)
13
+ end
14
+
15
+ # Generate the resolved config.
16
+ #
17
+ # @yield [filename, content]
18
+ # @yieldparam filename [String] The filename to use for the resolved config file (relative to +install_path+)
19
+ # @yieldparam content [String] The content for +filename+
20
+ def self.resolver_files(ip, port, tlds)
21
+ contents =
22
+ "# This file is generated by vagrant-dns\n" \
23
+ "[Resolve]\n" \
24
+ "DNS=#{ip}:#{port}\n" \
25
+ "Domains=~#{tlds.join(' ~')}\n"
26
+
27
+ yield "vagrant-dns.conf", contents
28
+ end
29
+
30
+ def install!
31
+ require 'fileutils'
32
+
33
+ src = File.join(tmp_path, "resolver", RESOLVED_CONFIG)
34
+ dest = File.join(install_path, RESOLVED_CONFIG)
35
+
36
+ commands = [
37
+ ['install', '-D', '-m', '0644', '-T', src.shellescape, dest.shellescape],
38
+ ['systemctl', 'reload-or-restart', 'systemd-resolved.service']
39
+ ]
40
+
41
+ exec(*commands)
42
+ end
43
+
44
+ def uninstall!
45
+ commands = [
46
+ ['rm', File.join(install_path, RESOLVED_CONFIG)],
47
+ ['systemctl', 'reload-or-restart', 'systemd-resolved.service']
48
+ ]
49
+
50
+ exec(*commands)
51
+ end
52
+
53
+ def purge!
54
+ require 'fileutils'
55
+ uninstall!
56
+ FileUtils.rm_r(tmp_path)
57
+ end
58
+
59
+ def exec(*commands)
60
+ return if !commands || commands.empty?
61
+
62
+ case exec_style
63
+ when :sudo
64
+ commands.each do |c|
65
+ system 'sudo', *c
66
+ end
67
+ else
68
+ raise ArgumentError, "Unsupported execution style: #{exec_style}. Use one of #{EXEC_STYLES.map(&:inspect).join(' ')}"
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -11,6 +11,22 @@ module VagrantDNS
11
11
  self.exec_style = options.fetch(:exec_style, :osa)
12
12
  end
13
13
 
14
+ # Generate the resolved config.
15
+ #
16
+ # @yield [filename, content]
17
+ # @yieldparam filename [String] The filename to use for the resolved config file (relative to +install_path+)
18
+ # @yieldparam content [String] The content for +filename+
19
+ def self.resolver_files(ip, port, tlds)
20
+ tlds.each do |tld|
21
+ contents =
22
+ "# This file is generated by vagrant-dns\n" \
23
+ "nameserver #{ip}\n" \
24
+ "port #{port}"
25
+
26
+ yield tld, contents
27
+ end
28
+ end
29
+
14
30
  def install!
15
31
  require 'fileutils'
16
32
 
@@ -26,8 +42,6 @@ module VagrantDNS
26
42
  end
27
43
 
28
44
  def uninstall!
29
- require 'fileutils'
30
-
31
45
  commands = registered_resolvers.map do |r|
32
46
  installed_resolver = File.join(install_path, File.basename(r))
33
47
  ['rm', '-rf', installed_resolver]
@@ -0,0 +1,16 @@
1
+ module VagrantDNS
2
+ module Installers
3
+ def self.resolve
4
+ if Vagrant::Util::Platform.darwin?
5
+ VagrantDNS::Installers::Mac
6
+ elsif Vagrant::Util::Platform.linux?
7
+ VagrantDNS::Installers::Linux
8
+ else
9
+ raise 'installing and uninstalling is only supported on Linux and macOS at the moment.'
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ require "vagrant-dns/installers/mac"
16
+ require "vagrant-dns/installers/linux"
@@ -1,60 +1,19 @@
1
- require 'yaml/store'
2
- require 'forwardable'
3
-
4
1
  module VagrantDNS
5
2
  # This is the dns pattern registry (aka "config")
6
3
  # It basically delegates everything to a YAML::Store but handles the conversion
7
4
  # of Regexp dns-patterns into YAML string keys and reverse.
8
5
  class Registry
9
- extend Forwardable
10
-
11
- def initialize(tmp_path)
12
- @store = YAML::Store.new(File.join(tmp_path, "config"), true)
13
- end
6
+ include VagrantDNS::Store
14
7
 
15
- def_delegators :@store, :transaction, :abort, :commit
16
-
17
- # This method is only valid in a #transaction and it cannot be read-only. It will raise PStore::Error if called at any other time.
18
- def [](name)
19
- name = name.source if name.respond_to? :source
20
- @store[name]
21
- end
22
-
23
- # This method is only valid in a #transaction and it cannot be read-only. It will raise PStore::Error if called at any other time.
24
- def []=(name, value)
25
- name = name.source if name.respond_to? :source
26
- @store[name] = value
27
- end
8
+ NAME = "config"
28
9
 
29
- def delete(name)
30
- name = name.source if name.respond_to? :source
31
- @store.delete(name)
32
- end
33
-
34
- # This method is only valid in a #transaction and it cannot be read-only. It will raise PStore::Error if called at any other time.
35
- def root?(name)
36
- name = name.source if name.respond_to? :source
37
- @store.root?(name)
38
- end
39
- alias_method :key?, :root?
40
-
41
- # This method is only valid in a #transaction and it cannot be read-only. It will raise PStore::Error if called at any other time.
42
- def roots
43
- @store.roots.map { |name| Regexp.new(name) }
44
- end
45
- alias_method :keys, :roots
46
-
47
- # This method is only valid in a #transaction and it cannot be read-only. It will raise PStore::Error if called at any other time.
48
- def any?
49
- @store.roots.any?
10
+ def initialize(tmp_path)
11
+ @store = YAML::Store.new(File.join(tmp_path, NAME), true)
50
12
  end
51
13
 
52
- def to_hash
53
- @store.transaction(true) do
54
- @store.roots.each_with_object({}) do |name, a|
55
- a[Regexp.new(name)] = @store[name]
56
- end
57
- end
14
+ # @return [VagrantDNS::Registry,nil] Eitehr an instance or +nil+ if cofig file does not exist.
15
+ def self.open(tmp_path)
16
+ new(tmp_path) if File.exist?(File.join(tmp_path, NAME))
58
17
  end
59
18
  end
60
19
  end
@@ -0,0 +1,82 @@
1
+ require 'pp'
2
+ require 'rubydns'
3
+ require 'async/dns/system'
4
+
5
+ module VagrantDNS
6
+ class Server
7
+ attr_reader :registry, :listen, :ttl, :resolver, :passthrough
8
+
9
+ def initialize(registry, listen:, ttl:, resolver:, passthrough:)
10
+ @registry = registry.to_hash
11
+ @listen = listen
12
+ @ttl = ttl
13
+
14
+ @resolver = if resolver.nil? || resolver == :system
15
+ RubyDNS::Resolver.new(Async::DNS::System.nameservers)
16
+ elsif !resolver || resolver.empty?
17
+ nil
18
+ else
19
+ RubyDNS::Resolver.new(resolver)
20
+ end
21
+
22
+ if passthrough && !resolver
23
+ puts "[Warning] 'passthrough' config has no effect, sice no passthrough resolver is set."
24
+ end
25
+
26
+ @passthrough = !!@resolver && passthrough
27
+ end
28
+
29
+ def run
30
+ # need those clusures for the +RubyDNS::run_server+ block
31
+ passthrough = self.passthrough
32
+ registry = self.registry
33
+ resolver = self.resolver
34
+ ttl = self.ttl
35
+
36
+ _passthrough = if passthrough
37
+ proc do |transaction|
38
+ transaction.passthrough!(resolver) do |response|
39
+ puts "Passthrough response: #{response.inspect}"
40
+ end
41
+ end
42
+ end
43
+
44
+ RubyDNS::run_server(listen) do
45
+ # match all known patterns first
46
+ registry.each do |pattern, ip|
47
+ match(pattern, Resolv::DNS::Resource::IN::A) do |transaction, match_data|
48
+ transaction.respond!(ip, ttl: ttl)
49
+ end
50
+ end
51
+
52
+ case passthrough
53
+ when true
54
+ # forward everything
55
+ otherwise(&_passthrough)
56
+ when false
57
+ # fail known patterns for non-A queries as NotImp
58
+ registry.each do |pattern, ip|
59
+ match(pattern) do |transaction, match_data|
60
+ transaction.fail!(:NotImp)
61
+ end
62
+ end
63
+
64
+ # unknown pattern end up as NXDomain
65
+ otherwise do |transaction|
66
+ transaction.fail!(:NXDomain)
67
+ end
68
+ when :unknown
69
+ # fail known patterns for non-A queries as NotImp
70
+ registry.each do |pattern, ip|
71
+ match(pattern) do |transaction, match_data|
72
+ transaction.fail!(:NotImp)
73
+ end
74
+ end
75
+
76
+ # forward only unknown patterns
77
+ otherwise(&_passthrough)
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -20,44 +20,27 @@ module VagrantDNS
20
20
  run!("status")
21
21
  end
22
22
 
23
- SERVER = Proc.new do |tmp_path, skip_require_dependencies|
24
- unless skip_require_dependencies
25
- require 'rubydns'
26
- require 'async/dns/system'
27
- end
28
-
29
- registry = Registry.new(tmp_path).to_hash
30
- std_resolver = RubyDNS::Resolver.new(Async::DNS::System.nameservers)
31
- ttl = VagrantDNS::Config.ttl
32
-
33
- RubyDNS::run_server(VagrantDNS::Config.listen) do
34
- registry.each do |pattern, ip|
35
- match(pattern, Resolv::DNS::Resource::IN::A) do |transaction, match_data|
36
- transaction.respond!(ip, ttl: ttl)
37
- end
38
- end
39
-
40
- otherwise do |transaction|
41
- transaction.passthrough!(std_resolver) do |reply, reply_name|
42
- puts reply
43
- puts reply_name
44
- end
45
- end
46
- end
47
- end
48
-
49
23
  def run!(cmd, opts = {})
50
24
  # On darwin, when the running Ruby is not compiled for the running OS
51
25
  # @see: https://github.com/BerlinVagrant/vagrant-dns/issues/72
52
26
  use_issue_72_workround = RUBY_PLATFORM.match?(/darwin/) && !RUBY_PLATFORM.end_with?(`uname -r`[0, 2])
53
27
 
54
28
  if cmd == "start" && use_issue_72_workround
55
- require 'rubydns'
56
- require 'async/dns/system'
29
+ require_relative "./server"
57
30
  end
58
31
 
59
32
  Daemons.run_proc("vagrant-dns", run_options(cmd, opts)) do
60
- SERVER.call(tmp_path, use_issue_72_workround)
33
+ unless use_issue_72_workround
34
+ require_relative "./server"
35
+ end
36
+
37
+ VagrantDNS::Server.new(
38
+ Registry.new(tmp_path),
39
+ listen: VagrantDNS::Config.listen,
40
+ ttl: VagrantDNS::Config.ttl,
41
+ passthrough: VagrantDNS::Config.passthrough,
42
+ resolver: VagrantDNS::Config.passthrough_resolver
43
+ ).run
61
44
  end
62
45
  end
63
46
 
@@ -67,20 +50,32 @@ module VagrantDNS
67
50
  end
68
51
 
69
52
  def show_config
70
- registry = Registry.new(tmp_path).to_hash
53
+ registry = Registry.open(tmp_path)
54
+ return unless registry
71
55
 
72
- if registry.any?
73
- registry.each do |pattern, ip|
56
+ config = registry.to_hash
57
+ if config.any?
58
+ config.each do |pattern, ip|
74
59
  puts format("%s => %s", pattern.inspect, ip)
75
60
  end
76
61
  else
77
- puts "Configuration missing or empty."
62
+ puts "Pattern configuration missing or empty."
78
63
  end
79
64
  end
80
65
 
81
- private
66
+ def show_tld_config
67
+ tld_registry = VagrantDNS::TldRegistry.open(tmp_path)
68
+ return unless tld_registry
69
+
70
+ tlds = tld_registry.transaction { |store| store.fetch("tlds", []) }
71
+ if !tlds || tlds.empty?
72
+ puts "No TLDs configured."
73
+ else
74
+ puts tlds
75
+ end
76
+ end
82
77
 
83
- def run_options(cmd, extra = {})
78
+ private def run_options(cmd, extra = {})
84
79
  daemon_dir = File.join(tmp_path, "daemon")
85
80
  {
86
81
  ARGV: [cmd],
@@ -89,7 +84,7 @@ module VagrantDNS
89
84
  log_output: true,
90
85
  log_dir: daemon_dir,
91
86
  **extra
92
- }
87
+ }
93
88
  end
94
89
  end
95
90
  end