vagrant-autodns 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,37 @@
1
+ #© 2014 LinkedIn Corp. All rights reserved.
2
+ #Licensed under the Apache License, Version 2.0 (the "License"); you may not
3
+ #use this file except in compliance with the License. You may obtain a copy of
4
+ #the License at http://www.apache.org/licenses/LICENSE-2.0
5
+
6
+ #Unless required by applicable law or agreed to in writing, software
7
+ #distributed under the License is distributed on an "AS IS" BASIS,
8
+ #WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9
+
10
+ module VagrantAutoDNS
11
+ class Command
12
+ class Add < Vagrant.plugin("2", :command)
13
+
14
+ def execute
15
+ require_relative '../autodnsdb'
16
+
17
+ opts = OptionParser.new do |optp|
18
+ optp.banner = "Usage: vagrant autodns add <hostname> <ip> [vagrant_id]"
19
+ end
20
+ argv = parse_options(opts) || return
21
+ #vagrant_id is an option parameter
22
+ raise Vagrant::Errors::CLIInvalidUsage, :help => opts.help.chomp if (argv.length < 2 || argv.length > 3)
23
+
24
+ hostname, ip, vagrant_id = argv
25
+
26
+ if VagrantAutoDNS.autodnsdb.update_record(hostname, ip, vagrant_id)
27
+ @env.ui.info("Entry #{ip} added for host #{hostname}")
28
+ 0
29
+ else
30
+ @env.ui.error("Entry #{ip} not added for host #{hostname}")
31
+ 1
32
+ end
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,34 @@
1
+ #© 2014 LinkedIn Corp. All rights reserved.
2
+ #Licensed under the Apache License, Version 2.0 (the "License"); you may not
3
+ #use this file except in compliance with the License. You may obtain a copy of
4
+ #the License at http://www.apache.org/licenses/LICENSE-2.0
5
+
6
+ #Unless required by applicable law or agreed to in writing, software
7
+ #distributed under the License is distributed on an "AS IS" BASIS,
8
+ #WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9
+
10
+ module VagrantAutoDNS
11
+ class Command
12
+ class Clear < Vagrant.plugin("2", :command)
13
+
14
+ def execute
15
+ require_relative '../autodnsdb'
16
+
17
+ opts = OptionParser.new do |optp|
18
+ optp.banner = "Usage: vagrant autodns clear"
19
+ end
20
+ argv = parse_options(opts) || return
21
+ #Takes no arguments
22
+ raise Vagrant::Errors::CLIInvalidUsage, :help => opts.help.chomp if (argv.length != 0)
23
+
24
+ if VagrantAutoDNS.autodnsdb.delete_all_records
25
+ @env.ui.info("DNS cleared")
26
+ 0
27
+ else
28
+ @env.ui.error("Something went wrong")
29
+ 1
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,34 @@
1
+ #© 2014 LinkedIn Corp. All rights reserved.
2
+ #Licensed under the Apache License, Version 2.0 (the "License"); you may not
3
+ #use this file except in compliance with the License. You may obtain a copy of
4
+ #the License at http://www.apache.org/licenses/LICENSE-2.0
5
+
6
+ #Unless required by applicable law or agreed to in writing, software
7
+ #distributed under the License is distributed on an "AS IS" BASIS,
8
+ #WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9
+
10
+ module VagrantAutoDNS
11
+ class Command
12
+ class Delete < Vagrant.plugin("2", :command)
13
+ def execute
14
+ require_relative '../autodnsdb'
15
+
16
+ opts = OptionParser.new do |optp|
17
+ optp.banner = "Usage: vagrant autodns delete <hostname>"
18
+ end
19
+ argv = parse_options(opts) || return
20
+ raise Vagrant::Errors::CLIInvalidUsage, :help => opts.help.chomp if (argv.length != 1)
21
+
22
+ db = VagrantAutoDNS.autodnsdb
23
+ hostname = argv[0]
24
+ if db.delete_record(hostname)
25
+ @env.ui.info("Host #{hostname} deleted")
26
+ 0
27
+ else
28
+ @env.ui.error("Host #{hostname} not deleted")
29
+ 1
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,36 @@
1
+ #© 2014 LinkedIn Corp. All rights reserved.
2
+ #Licensed under the Apache License, Version 2.0 (the "License"); you may not
3
+ #use this file except in compliance with the License. You may obtain a copy of
4
+ #the License at http://www.apache.org/licenses/LICENSE-2.0
5
+
6
+ #Unless required by applicable law or agreed to in writing, software
7
+ #distributed under the License is distributed on an "AS IS" BASIS,
8
+ #WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9
+
10
+ module VagrantAutoDNS
11
+ class Command
12
+ class List < Vagrant.plugin("2", :command)
13
+
14
+ def execute
15
+ require_relative '../autodnsdb'
16
+
17
+ opts = OptionParser.new do |optp|
18
+ optp.banner = "Usage: vagrant autodns list"
19
+ end
20
+ argv = parse_options(opts) || return
21
+ #Takes no arguments
22
+ raise Vagrant::Errors::CLIInvalidUsage, :help => opts.help.chomp if (argv.length != 0)
23
+
24
+ records = VagrantAutoDNS.autodnsdb.list_all_records
25
+ if records.empty?
26
+ @env.ui.error("No records found")
27
+ else
28
+ records.each do |record|
29
+ @env.ui.success("Host #{record["hostname"]} has IP #{record["ip"]}")
30
+ end
31
+ end
32
+ 0
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,28 @@
1
+ #© 2014 LinkedIn Corp. All rights reserved.
2
+ #Licensed under the Apache License, Version 2.0 (the "License"); you may not
3
+ #use this file except in compliance with the License. You may obtain a copy of
4
+ #the License at http://www.apache.org/licenses/LICENSE-2.0
5
+
6
+ #Unless required by applicable law or agreed to in writing, software
7
+ #distributed under the License is distributed on an "AS IS" BASIS,
8
+ #WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9
+
10
+ module VagrantAutoDNS
11
+ class Command
12
+ class Reload < Vagrant.plugin("2", :command)
13
+ def execute
14
+ require_relative '../daemon'
15
+
16
+ opts = OptionParser.new do |optp|
17
+ optp.banner = "Usage: vagrant autodns [re]start"
18
+ end
19
+ argv = parse_options(opts) || return
20
+ #Takes no arguments
21
+ raise Vagrant::Errors::CLIInvalidUsage, :help => opts.help.chomp if (argv.length != 0)
22
+
23
+ #TODO: Fixme
24
+ 0
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,28 @@
1
+ #© 2014 LinkedIn Corp. All rights reserved.
2
+ #Licensed under the Apache License, Version 2.0 (the "License"); you may not
3
+ #use this file except in compliance with the License. You may obtain a copy of
4
+ #the License at http://www.apache.org/licenses/LICENSE-2.0
5
+
6
+ #Unless required by applicable law or agreed to in writing, software
7
+ #distributed under the License is distributed on an "AS IS" BASIS,
8
+ #WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9
+
10
+ module VagrantAutoDNS
11
+ class Command
12
+ class Restart < Vagrant.plugin("2", :command)
13
+ def execute
14
+ require_relative '../daemon'
15
+
16
+ opts = OptionParser.new do |optp|
17
+ optp.banner = "Usage: vagrant autodns [re]start"
18
+ end
19
+ argv = parse_options(opts) || return
20
+ #Takes no arguments
21
+ raise Vagrant::Errors::CLIInvalidUsage, :help => opts.help.chomp if (argv.length != 0)
22
+
23
+ VagrantAutoDNS::Daemon.restart
24
+ 0
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,28 @@
1
+ #© 2014 LinkedIn Corp. All rights reserved.
2
+ #Licensed under the Apache License, Version 2.0 (the "License"); you may not
3
+ #use this file except in compliance with the License. You may obtain a copy of
4
+ #the License at http://www.apache.org/licenses/LICENSE-2.0
5
+
6
+ #Unless required by applicable law or agreed to in writing, software
7
+ #distributed under the License is distributed on an "AS IS" BASIS,
8
+ #WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9
+
10
+ module VagrantAutoDNS
11
+ class Command
12
+ class Status < Vagrant.plugin("2", :command)
13
+ def execute
14
+ require_relative '../daemon'
15
+
16
+ opts = OptionParser.new do |optp|
17
+ optp.banner = "Usage: vagrant autodns [re]start"
18
+ end
19
+ argv = parse_options(opts) || return
20
+ #Takes no arguments
21
+ raise Vagrant::Errors::CLIInvalidUsage, :help => opts.help.chomp if (argv.length != 0)
22
+
23
+ @env.ui.success("Daemon status is #{VagrantAutoDNS::Daemon.daemon_status}")
24
+ 0
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,28 @@
1
+ #© 2014 LinkedIn Corp. All rights reserved.
2
+ #Licensed under the Apache License, Version 2.0 (the "License"); you may not
3
+ #use this file except in compliance with the License. You may obtain a copy of
4
+ #the License at http://www.apache.org/licenses/LICENSE-2.0
5
+
6
+ #Unless required by applicable law or agreed to in writing, software
7
+ #distributed under the License is distributed on an "AS IS" BASIS,
8
+ #WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9
+
10
+ module VagrantAutoDNS
11
+ class Command
12
+ class Stop < Vagrant.plugin("2", :command)
13
+ def execute
14
+ require_relative '../daemon'
15
+
16
+ opts = OptionParser.new do |optp|
17
+ optp.banner = "Usage: vagrant autodns [re]start"
18
+ end
19
+ argv = parse_options(opts) || return
20
+ #Takes no arguments
21
+ raise Vagrant::Errors::CLIInvalidUsage, :help => opts.help.chomp if (argv.length != 0)
22
+
23
+ VagrantAutoDNS::Daemon.stop
24
+ 0
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,110 @@
1
+ #© 2014 LinkedIn Corp. All rights reserved.
2
+ #Licensed under the Apache License, Version 2.0 (the "License"); you may not
3
+ #use this file except in compliance with the License. You may obtain a copy of
4
+ #the License at http://www.apache.org/licenses/LICENSE-2.0
5
+
6
+ #Unless required by applicable law or agreed to in writing, software
7
+ #distributed under the License is distributed on an "AS IS" BASIS,
8
+ #WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9
+
10
+ require_relative 'daemon'
11
+
12
+ module VagrantAutoDNS
13
+ class Config < Vagrant.plugin('2', :config)
14
+
15
+ # @return [Boolean]
16
+ attr_accessor :enabled
17
+
18
+ # @return [String, Array] (default System resolver)
19
+ attr_accessor :resolver
20
+
21
+ # @return [String] Valid IPV4 address (default 127.0.0.1)
22
+ attr_accessor :listen_ip
23
+
24
+ # @return [Fixnum] Port number (default 15353)
25
+ attr_accessor :listen_port
26
+
27
+ # @return [String] IPv6 address
28
+ attr_accessor :listen_ipv6
29
+
30
+ # @return [Boolean] Enable ipv6
31
+ attr_accessor :enable_ipv6
32
+
33
+ # @return [String] Directory to store data and state
34
+ attr_accessor :working_directory
35
+
36
+ # @return [String] DB File name
37
+ attr_accessor :db_file
38
+
39
+ DAEMON_DEFAULT_OPTS = {
40
+ :working_directory => '.vagrant/autodns',
41
+ :listen_ip => '127.0.0.1',
42
+ :listen_port => 15353,
43
+ :resolver => :system, #Deamon defaults to system
44
+ :db_file => 'autodns.db',
45
+ :enable_ipv6 => true,
46
+ }
47
+
48
+ CLIENT_DEFAULT_OPTS = {
49
+ :aliases => []
50
+ }
51
+
52
+ DEFAULT_VALUES = {
53
+ :enabled => false
54
+ }.merge(DAEMON_DEFAULT_OPTS).merge(CLIENT_DEFAULT_OPTS)
55
+
56
+ def initialize
57
+ super
58
+ DEFAULT_VALUES.keys.each do |default_key|
59
+ instance_key = instance_var(default_key)
60
+ instance_variable_set(instance_key, UNSET_VALUE)
61
+ end
62
+ end
63
+
64
+ def alias(*aliases)
65
+ @aliases.concat(aliases)
66
+ end
67
+
68
+ def enable
69
+ @enabled = true
70
+ end
71
+
72
+ def disable
73
+ @enabled = false
74
+ end
75
+
76
+ def enabled?
77
+ #Coerce to boolean
78
+ !!@enabled
79
+ end
80
+
81
+ def finalize!
82
+ #special handling for working_directory
83
+ expand_working_directory
84
+
85
+ DEFAULT_VALUES.each do |default_key, default_value|
86
+ instance_key = instance_var(default_key)
87
+ if instance_variable_get(instance_key) == UNSET_VALUE
88
+ instance_variable_set(instance_key, default_value)
89
+ end
90
+ next unless DAEMON_DEFAULT_OPTS.has_key?(default_key)
91
+ Daemon.send(:instance_variable_set, instance_key, instance_variable_get(instance_key))
92
+ end
93
+ end
94
+
95
+ private
96
+
97
+ def expand_working_directory
98
+ case @working_directory
99
+ when UNSET_VALUE
100
+ @working_directory = File.expand_path DAEMON_DEFAULT_OPTS[:working_directory]
101
+ else
102
+ @working_directory = File.expand_path working_directory
103
+ end
104
+ end
105
+
106
+ def instance_var(key)
107
+ '@' + key.to_s
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,172 @@
1
+ #© 2014 LinkedIn Corp. All rights reserved.
2
+ #Licensed under the Apache License, Version 2.0 (the "License"); you may not
3
+ #use this file except in compliance with the License. You may obtain a copy of
4
+ #the License at http://www.apache.org/licenses/LICENSE-2.0
5
+
6
+ #Unless required by applicable law or agreed to in writing, software
7
+ #distributed under the License is distributed on an "AS IS" BASIS,
8
+ #WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9
+
10
+ require 'rexec'
11
+ require 'rexec/daemon'
12
+ require 'rubydns'
13
+ require 'rubydns/resolver'
14
+ require 'rubydns/system'
15
+ require 'rainbow/ext/string'
16
+ require_relative 'autodnsdb'
17
+
18
+ module VagrantAutoDNS
19
+ class Daemon < RExec::Daemon::Base
20
+
21
+ @@base_directory = nil
22
+
23
+ ProcessFile = RExec::Daemon::ProcessFile
24
+ A_RECORD = Resolv::DNS::Resource::IN::A
25
+ AAAA_RECORD = Resolv::DNS::Resource::IN::AAAA
26
+
27
+ class << self
28
+ attr_accessor :working_directory, :resolver, :db_file
29
+ attr_accessor :listen_ip, :listen_port, :enable_ipv6
30
+ end
31
+
32
+ def self.run
33
+ upstream = upstream_resolver
34
+ db_lib = AutoDNSDB
35
+ db_absolute_path = autodnsdb_path
36
+ allow_ipv6 = ipv6_enabled?
37
+ RubyDNS::run_server(:listen => listen_interface) do
38
+ db = db_lib.new(db_absolute_path)
39
+ match(//, A_RECORD) do |transaction|
40
+ domain = transaction.name.to_s.encode('US-ASCII')
41
+ record = db.find_record(domain)
42
+ if record
43
+ transaction.respond!(record['ip'])
44
+ else
45
+ transaction.passthrough!(upstream)
46
+ end
47
+ end
48
+
49
+ match(//, AAAA_RECORD) do |transaction|
50
+ if allow_ipv6
51
+ transaction.passthrough!(upstream)
52
+ else
53
+ transaction.fail!(:NXDomain)
54
+ end
55
+ end
56
+
57
+ # Default DNS handler
58
+ otherwise do |transaction|
59
+ transaction.passthrough!(upstream)
60
+ end
61
+ end
62
+ end
63
+
64
+ def self.restart
65
+ stop unless stopped?
66
+ start
67
+ end
68
+
69
+ def self.ensure_running
70
+ restart unless running?
71
+ end
72
+
73
+ def self.daemon_status
74
+ ProcessFile.status(self)
75
+ end
76
+
77
+ def self.pid
78
+ ProcessFile.recall(self)
79
+ end
80
+
81
+ def self.running?
82
+ daemon_status == :running
83
+ end
84
+
85
+ def self.stopped?
86
+ daemon_status == :stopped
87
+ end
88
+
89
+ def self.ipv6_enabled?
90
+ @enable_ipv6.nil? || @enable_ipv6
91
+ end
92
+
93
+ def self.autodnsdb
94
+ return @autodnsdb if @autodnsdb.is_a?(AutoDNSDB)
95
+ @autodnsdb = AutoDNSDB.new(autodnsdb_path)
96
+ end
97
+
98
+ def self.autodnsdb_path
99
+ @autodnsdb_path ||= File.expand_path(File.join(working_directory, db_file))
100
+ end
101
+
102
+ def self.working_directory=(dir)
103
+ @working_directory = File.expand_path(dir)
104
+ end
105
+
106
+ private
107
+
108
+ # RubyDNS upstream resolver
109
+ def self.upstream_resolver
110
+ #Bail out if already set
111
+ return @resolver if @resolver.is_a?(RubyDNS::Resolver)
112
+ #Case statment must set new_resolver to a valid interface
113
+ case @resolver
114
+ when String
115
+ # IP or IP/PORT combo
116
+ new_resolver = to_interface(@resolver)
117
+ when Array
118
+ #If 2 strings are provided assume they are IPs
119
+ if @resolver.length == 2 && @resolver.all?{|v| v.is_a? String}
120
+ new_resolver = @resolver.map{|ip| to_interface(ip)}.flatten(1)
121
+ #Else expect array formated in interface form
122
+ else
123
+ new_resolver = @resolver
124
+ end
125
+ when Symbol
126
+ #Use the predefined custom_resolvers
127
+ new_resolver = custom_resolver(@resolver)
128
+ when NilClass
129
+ #Default to system_resolver
130
+ new_resolver = system_resolver
131
+ end
132
+ @resolver = RubyDNS::Resolver.new(new_resolver)
133
+ end
134
+
135
+ def self.listen_interface
136
+ to_interface(listen_ip, listen_port)
137
+ end
138
+
139
+ def self.custom_resolver(resolver = :random)
140
+ return system_resolver if resolver == :system
141
+ #TODO: move to config file somewhere
142
+ hosted_resolvers = {
143
+ :level3 => ['209.244.0.3', '209.244.0.4'],
144
+ :google => ['8.8.8.8', '8.8.4.4'],
145
+ :securly => ['184.169.143.224', '184.169.161.155'],
146
+ :comodo => ['8.26.56.26', '8.20.247.20'],
147
+ :opendns => ['208.67.222.222', '208.67.220.220'],
148
+ :norton => ['198.153.192.40', '198.153.194.40'],
149
+ :opennic => ['216.87.84.211', '23.90.4.6']
150
+ }
151
+ resolver = hosted_resolvers.keys.sample if resolver == :random
152
+ resolver_ip = hosted_resolvers[resolver]
153
+ resolver_ip.map{|ip| to_interface(ip)}.flatten(1)
154
+ end
155
+
156
+ def self.system_resolver
157
+ RubyDNS::System::nameservers
158
+ end
159
+
160
+ IPV4_6_PORT_REGEX =
161
+ /^(?:[0-9.]+|(?:\[[0-9a-fA-F:]+\]))(:[0-9]+)?$/
162
+
163
+ def self.to_interface(ip, port = 53)
164
+ #Try to extract IP and PORT from IP
165
+ ip_r, port_r = ip.match(IPV4_6_PORT_REGEX).to_a
166
+ #If regex found port, set it accordingly
167
+ # remove preceding semi-colon
168
+ port = port_r[1..-1].to_i if port_r
169
+ [[:udp, ip_r, port], [:tcp, ip_r, port]]
170
+ end
171
+ end
172
+ end