vagrant-dns 2.2.3 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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