devdnsd 3.1.2 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -2
  3. data/.rubocop.yml +82 -0
  4. data/.travis-gemfile +4 -10
  5. data/.travis.yml +11 -5
  6. data/CHANGELOG.md +11 -0
  7. data/Gemfile +9 -8
  8. data/README.md +4 -5
  9. data/Rakefile +22 -6
  10. data/bin/devdnsd +42 -36
  11. data/config/devdnsd_config.sample +11 -11
  12. data/devdnsd.gemspec +7 -6
  13. data/doc/DevDNSd.html +6 -6
  14. data/doc/DevDNSd/{ApplicationMethods/Aliases.html → Aliases.html} +96 -100
  15. data/doc/DevDNSd/Application.html +2170 -1084
  16. data/doc/DevDNSd/Configuration.html +63 -33
  17. data/doc/DevDNSd/Errors.html +3 -3
  18. data/doc/DevDNSd/Errors/InvalidRule.html +3 -3
  19. data/doc/DevDNSd/{ApplicationMethods/System.html → OSX.html} +116 -489
  20. data/doc/DevDNSd/Rule.html +448 -749
  21. data/doc/DevDNSd/{ApplicationMethods/Server.html → Server.html} +77 -73
  22. data/doc/DevDNSd/System.html +895 -0
  23. data/doc/DevDNSd/Version.html +6 -6
  24. data/doc/_index.html +28 -27
  25. data/doc/class_list.html +6 -2
  26. data/doc/file.README.html +8 -9
  27. data/doc/file_list.html +5 -1
  28. data/doc/frames.html +1 -1
  29. data/doc/index.html +8 -9
  30. data/doc/js/full_list.js +4 -1
  31. data/doc/method_list.html +106 -84
  32. data/doc/top-level-namespace.html +3 -3
  33. data/lib/devdnsd.rb +10 -8
  34. data/lib/devdnsd/aliases.rb +171 -0
  35. data/lib/devdnsd/application.rb +93 -704
  36. data/lib/devdnsd/configuration.rb +20 -11
  37. data/lib/devdnsd/errors.rb +1 -1
  38. data/lib/devdnsd/osx.rb +217 -0
  39. data/lib/devdnsd/rule.rb +65 -94
  40. data/lib/devdnsd/server.rb +118 -0
  41. data/lib/devdnsd/system.rb +102 -0
  42. data/lib/devdnsd/version.rb +3 -3
  43. data/locales/en.yml +17 -16
  44. data/locales/it.yml +17 -16
  45. data/spec/devdnsd/application_spec.rb +188 -184
  46. data/spec/devdnsd/configuration_spec.rb +2 -2
  47. data/spec/devdnsd/rule_spec.rb +33 -34
  48. data/spec/resolver_helper.rb +10 -27
  49. data/spec/spec_helper.rb +21 -7
  50. metadata +36 -21
  51. data/doc/DevDNSd/ApplicationMethods.html +0 -125
  52. data/doc/DevDNSd/ApplicationMethods/System/ClassMethods.html +0 -538
  53. data/spec/coverage_helper.rb +0 -20
@@ -65,28 +65,37 @@ module DevDNSd
65
65
  super(file, overrides, logger)
66
66
 
67
67
  # Make sure some arguments are of correct type
68
- self.log_file = case log_file
69
- when "STDOUT" then $stdout
70
- when "STDERR" then $stderr
71
- else File.absolute_path(File.expand_path(log_file))
72
- end
68
+ self.log_file = resolve_log_file
73
69
 
74
70
  self.pid_file = File.absolute_path(File.expand_path(pid_file))
75
71
  self.port = port.to_integer
76
72
  self.log_level = log_level.to_integer
77
73
 
78
74
  # Add a default rule
79
- add_rule(/.+/, "127.0.0.1") if rules.blank?
75
+ add_rule(match: /.+/, reply: "127.0.0.1") if rules.blank?
80
76
  end
81
77
 
82
78
  # Adds a rule to the configuration.
83
79
  #
84
- # @param args [Array] The rule's arguments.
85
- # @param block [Proc] An optional block for the rule.
80
+ # @param match [String|Regexp] The pattern to match.
81
+ # @param reply [String|Symbol] The IP or hostname to reply back to the client. It can be omitted (and it will be ignored) if a block is provided.
82
+ # @param type [Symbol] The type of request to match.
83
+ # @param options [Hash] A list of options for the request.
84
+ # @param block [Proc] An optional block to compute the reply instead of using the `reply` parameter.
86
85
  # @return [Array] The current set of rule.
87
- # @see Rule.create
88
- def add_rule(*args, &block)
89
- rules << DevDNSd::Rule.create(*args, &block)
86
+ def add_rule(match: /.+/, reply: "127.0.0.1", type: :A, options: {}, &block)
87
+ rules << DevDNSd::Rule.create(match: match, reply: reply, type: type, options: options, &block)
88
+ end
89
+
90
+ private
91
+
92
+ # :nodoc:
93
+ def resolve_log_file
94
+ case log_file
95
+ when "STDOUT" then $stdout
96
+ when "STDERR" then $stderr
97
+ else File.absolute_path(File.expand_path(log_file))
98
+ end
90
99
  end
91
100
  end
92
101
  end
@@ -11,4 +11,4 @@ module DevDNSd
11
11
  class InvalidRule < ::ArgumentError
12
12
  end
13
13
  end
14
- end
14
+ end
@@ -0,0 +1,217 @@
1
+ # This file is part of the devdnsd gem. Copyright (C) 2013 and above Shogun <shogun@cowtech.it>.
2
+ # Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
3
+ #
4
+
5
+ # A small DNS server to enable local .dev domain resolution.
6
+ module DevDNSd
7
+ # OSX management methods.
8
+ module OSX
9
+ # Gets the path for the resolver file.
10
+ #
11
+ # @param tld [String] The TLD to manage.
12
+ # @return [String] The path for the resolver file.
13
+ def resolver_path(tld = nil)
14
+ tld ||= @config.tld
15
+ "/etc/resolver/#{tld}"
16
+ end
17
+
18
+ # Gets the path for the launch agent file.
19
+ #
20
+ # @param name [String] The base name for the agent.
21
+ # @return [String] The path for the launch agent file.
22
+ def launch_agent_path(name = "it.cowtech.devdnsd")
23
+ ENV["HOME"] + "/Library/LaunchAgents/#{name}.plist"
24
+ end
25
+
26
+ # Executes a shell command.
27
+ #
28
+ # @param command [String] The command to execute.
29
+ # @return [Boolean] `true` if command succeeded, `false` otherwise.
30
+ def execute_command(command)
31
+ system("#{command} 2>&1 > /dev/null")
32
+ end
33
+
34
+ # Updates DNS cache.
35
+ #
36
+ # @return [Boolean] `true` if command succeeded, `false` otherwise.
37
+ def dns_update
38
+ @logger.info(i18n.dns_update)
39
+
40
+ script = Tempfile.new("devdnsd-dns-cache-script")
41
+ script.write("dscacheutil -flushcache 2>&1 > /dev/null\n")
42
+ script.write("killall -9 mDNSResponder 2>&1 > /dev/null\n")
43
+ script.write("killall -9 mDNSResponderHelper 2>&1 > /dev/null\n")
44
+ script.close
45
+
46
+ Kernel.system("/usr/bin/osascript -e 'do shell script \"sh #{script.path}\" with administrator privileges' 2>&1 > /dev/null")
47
+ script.unlink
48
+ end
49
+
50
+ # Checks if we are running on MacOS X.
51
+ #
52
+ # System services are only available on that platform.
53
+ #
54
+ # @return [Boolean] `true` if the current platform is MacOS X, `false` otherwise.
55
+ def osx?
56
+ ::RbConfig::CONFIG['host_os'] =~ /^darwin/
57
+ end
58
+ alias_method :is_osx?, :osx?
59
+
60
+ # Adds aliases to an interface.
61
+ #
62
+ # @param options [Hash] The options provided by the user.
63
+ # @return [Boolean] `true` if action succeeded, `false` otherwise.
64
+ def action_add(options)
65
+ manage_aliases(:add, i18n.add_empty, options)
66
+ end
67
+
68
+ # Removes aliases from an interface.
69
+ #
70
+ # @param options [Hash] The options provided by the user.
71
+ # @return [Boolean] `true` if action succeeded, `false` otherwise.
72
+ def action_remove(options)
73
+ manage_aliases(:remove, i18n.remove_empty, options)
74
+ end
75
+
76
+ # Installs the application into the autolaunch.
77
+ #
78
+ # @return [Boolean] `true` if action succeeded, `false` otherwise.
79
+ def action_install
80
+ manage_installation(launch_agent_path, resolver_path, :create_resolver, :create_agent, :load_agent)
81
+ end
82
+
83
+ # Uninstalls the application from the autolaunch.
84
+ #
85
+ # @return [Boolean] `true` if action succeeded, `false` otherwise.
86
+ def action_uninstall
87
+ manage_installation(launch_agent_path, resolver_path, :delete_resolver, :unload_agent, :delete_agent)
88
+ end
89
+
90
+ private
91
+
92
+ # :nodoc:
93
+ def log_status(pid, status)
94
+ logger.info(status == :running ? replace_markers(i18n.status_running(pid)) : replace_markers(i18n.send("status_#{status}")))
95
+ end
96
+
97
+ # :nodoc:
98
+ def manage_installation(launch_agent, resolver_path, first_operation, second_operation, third_operation)
99
+ rv = check_agent_available
100
+
101
+ logger.warn(replace_markers(i18n.admin_privileges_warning))
102
+ rv = send(first_operation, launch_agent, resolver_path) if rv
103
+ rv = send(second_operation, launch_agent, resolver_path) if rv
104
+ rv = send(third_operation, launch_agent, resolver_path) if rv
105
+ dns_update
106
+ rv
107
+ end
108
+
109
+ # :nodoc:
110
+ def delete_file(file, before_message, error_message)
111
+ logger.info(i18n.send(before_message, file))
112
+ ::File.delete(file)
113
+ true
114
+ rescue
115
+ logger.warn(i18n.send(error_message))
116
+ false
117
+ end
118
+
119
+ # :nodoc:
120
+ def check_agent_available
121
+ rv = true
122
+ unless osx?
123
+ logger.fatal(i18n.no_agent)
124
+ rv = false
125
+ end
126
+
127
+ rv
128
+ end
129
+
130
+ # :nodoc:
131
+ def create_resolver(_, resolver_path)
132
+ logger.info(replace_markers(i18n.resolver_creating(resolver_path)))
133
+
134
+ script_file = create_resolver_script(resolver_path)
135
+ Kernel.system("/usr/bin/osascript -e 'do shell script \"sh #{script_file.path}\" with administrator privileges' 2>&1 > /dev/null")
136
+ script_file.unlink
137
+ true
138
+ rescue
139
+ logger.error(i18n.resolver_creating_error)
140
+ false
141
+ end
142
+
143
+ # :nodoc:
144
+ def create_resolver_script(resolver_path)
145
+ script = "mkdir -p '#{File.dirname(resolver_path)}'\nrm -rf '#{resolver_path}'\necho 'nameserver 127.0.0.1\\nport #{@config.port}' >> '#{resolver_path}'"
146
+ f = Tempfile.new("devdnsd-install-script")
147
+ f.write(script)
148
+ f.close
149
+ f
150
+ end
151
+
152
+ # :nodoc:
153
+ def delete_resolver(_, resolver_path)
154
+ logger.info(i18n.resolver_deleting(resolver_path))
155
+ Kernel.system("/usr/bin/osascript -e 'do shell script \"rm #{resolver_path}\" with administrator privileges' 2>&1 > /dev/null")
156
+ true
157
+ rescue
158
+ logger.warn(i18n.resolver_deleting_error)
159
+ false
160
+ end
161
+
162
+ # :nodoc:
163
+ def create_agent(launch_agent, _)
164
+ logger.info(replace_markers(i18n.agent_creating(launch_agent)))
165
+ program, args = prepare_agent
166
+
167
+ ::File.open(launch_agent, "w") do |f|
168
+ f.write({"KeepAlive" => true, "Label" => "it.cowtech.devdnsd", "Program" => program, "ProgramArguments" => args, "RunAtLoad" => true}.to_plist)
169
+ f.flush
170
+ end
171
+
172
+ true
173
+ rescue
174
+ logger.error(i18n.agent_creating_error)
175
+ false
176
+ end
177
+
178
+ # :nodoc:
179
+ def prepare_agent
180
+ [
181
+ (::Pathname.new(Dir.pwd) + $PROGRAM_NAME).to_s,
182
+ (ARGV ? ARGV[0, ARGV.length - 1] : [])
183
+ ]
184
+ end
185
+
186
+ # :nodoc:
187
+ def delete_agent(launch_agent, _)
188
+ delete_file(launch_agent, :agent_deleting, :agent_deleting_error)
189
+ end
190
+
191
+ # :nodoc:
192
+ def load_agent(launch_agent, _)
193
+ toggle_agent(launch_agent, "load", :agent_loading, :agent_loading_error, :error)
194
+ end
195
+
196
+ # :nodoc:
197
+ def unload_agent(launch_agent, _)
198
+ toggle_agent(launch_agent, "unload", :agent_unloading, :agent_unloading_error, :warn)
199
+ end
200
+
201
+ # :nodoc:
202
+ def toggle_agent(launch_agent, operation, info_message, error_message, error_level)
203
+ logger.info(i18n.send(info_message, launch_agent))
204
+ raise RuntimeError unless File.exist?(launch_agent)
205
+ execute_command("launchctl #{operation} -w \"#{launch_agent}\" > /dev/null 2>&1")
206
+ true
207
+ rescue
208
+ logger.send(error_level, i18n.send(error_message))
209
+ false
210
+ end
211
+
212
+ # :nodoc:
213
+ def replace_markers(message)
214
+ @command.application.console.replace_markers(message)
215
+ end
216
+ end
217
+ end
@@ -24,19 +24,54 @@ module DevDNSd
24
24
  attr_accessor :options
25
25
  attr_accessor :block
26
26
 
27
- include Lazier::I18n
27
+ # Class methods
28
+ class << self
29
+ # Creates a new rule.
30
+ #
31
+ # @param match [String|Regexp] The pattern to match.
32
+ # @param reply [String|Symbol] The IP or hostname to reply back to the client. It can be omitted (and it will be ignored) if a block is provided.
33
+ # @param type [Symbol] The type of request to match.
34
+ # @param options [Hash] A list of options for the request.
35
+ # @param block [Proc] An optional block to compute the reply instead of using the `reply` parameter.
36
+ # @return [Rule] The new rule.
37
+ def create(match: /.+/, reply: "127.0.0.1", type: :A, options: {}, &block)
38
+ new(match: match, reply: reply, type: type, options: options, &block)
39
+ end
40
+
41
+ # Converts a class to the correspondent symbol.
42
+ #
43
+ # @param klass [Class] The class to convert.
44
+ # @return [Symbol] The symbol representation of the class.
45
+ def resource_class_to_symbol(klass)
46
+ klass.to_s.gsub(/(.+::)?(.+)/, "\\2").to_sym
47
+ end
48
+
49
+ # Converts a symbol to the correspondent DNS resource class.
50
+ #
51
+ # @param symbol [Symbol] The symbol to convert.
52
+ # @param locale [Symbol] The locale to use for the messages.
53
+ # @return [Symbol] The class associated to the symbol.
54
+ def symbol_to_resource_class(symbol, locale = nil)
55
+ symbol = symbol.to_s.upcase
56
+
57
+ begin
58
+ "Resolv::DNS::Resource::IN::#{symbol}".constantize
59
+ rescue ::NameError
60
+ i18n = Bovem::I18n.new(locale, root: "devdnsd", path: ::Pathname.new(::File.dirname(__FILE__)).to_s + "/../../locales/")
61
+ raise(DevDNSd::Errors::InvalidRule, i18n.rule_invalid_resource(symbol))
62
+ end
63
+ end
64
+ end
28
65
 
29
66
  # Creates a new rule.
30
67
  #
31
68
  # @param match [String|Regexp] The pattern to match.
32
- # @param reply [String] The IP or hostname to reply back to the client.
69
+ # @param reply [String|Symbol] The IP or hostname to reply back to the client. It can be omitted (and it will be ignored) if a block is provided.
33
70
  # @param type [Symbol] The type of request to match.
34
71
  # @param options [Hash] A list of options for the request.
35
72
  # @param block [Proc] An optional block to compute the reply instead of using the `reply` parameter.
36
73
  # @see .create
37
- def initialize(match = /.+/, reply = "127.0.0.1", type = :A, options = {}, &block)
38
- i18n_setup(:devdnsd, ::File.absolute_path(::Pathname.new(::File.dirname(__FILE__)).to_s + "/../../locales/"))
39
- self.i18n = options[:locale]
74
+ def initialize(match: /.+/, reply: "127.0.0.1", type: :A, options: {}, &block)
40
75
  setup(match, reply, type, options, block)
41
76
  validate_rule
42
77
  end
@@ -45,122 +80,58 @@ module DevDNSd
45
80
  #
46
81
  # @return [Array|Class] The class(es) for the current rule.
47
82
  def resource_class
48
- classes = @type.ensure_array(nil, true, true, true) {|cls| self.class.symbol_to_resource_class(cls, options[:locale]) }
83
+ classes = @type.ensure_array(no_duplicates: true, compact: true, flatten: true) { |cls| self.class.symbol_to_resource_class(cls, options[:locale]) }
49
84
  classes.length == 1 ? classes.first : classes
50
85
  end
51
86
 
52
87
  # Checks if the rule is a regexp.
53
88
  #
54
89
  # @return [Boolean] `true` if the rule is a Regexp, `false` otherwise.
55
- def is_regexp?
90
+ def regexp?
56
91
  @match.is_a?(::Regexp)
57
92
  end
93
+ alias_method :is_regexp?, :regexp?
58
94
 
59
95
  # Checks if the rule is a regexp.
60
96
  #
61
97
  # @return [Boolean] `true` if the rule has a block, `false` otherwise.
62
- def has_block?
98
+ def block?
63
99
  @block.present?
64
100
  end
101
+ alias_method :has_block?, :block?
65
102
 
66
103
  # Matches a hostname to the rule.
67
104
  #
68
105
  # @param hostname [String] The hostname to match.
69
106
  # @return [MatchData|Boolean|Nil] Return `true` or MatchData (if the pattern is a regexp) if the rule matches, `false` or `nil` otherwise.
70
107
  def match_host(hostname)
71
- is_regexp? ? @match.match(hostname) : (@match == hostname)
72
- end
73
-
74
- # Creates a new rule.
75
- #
76
- # @param match [String|Regexp] The pattern to match.
77
- # @param reply_or_type [String|Symbol] The IP or hostname to reply back to the client (or the type of request to match, if a block is provided).
78
- # @param type [Symbol] The type of request to match. This is ignored if a block is provided.
79
- # @param options [Hash] A list of options for the request.
80
- # @param block [Proc] An optional block to compute the reply instead of using the `reply_or_type` parameter. In this case `reply_or_type` is used for the type of the request and `type` is ignored.
81
- # @return [Rule] The new rule.
82
- def self.create(match, reply_or_type = nil, type = nil, options = {}, &block)
83
- validate_options(reply_or_type, options, block, Lazier::Localizer.new(:devdnsd, ::File.absolute_path(::Pathname.new(::File.dirname(__FILE__)).to_s + "/../../locales/"), options.is_a?(Hash) ? options[:locale] : nil))
84
- setup(new(match), reply_or_type, type, options, block)
85
- end
86
-
87
- # Converts a class to the correspondent symbol.
88
- #
89
- # @param klass [Class] The class to convert.
90
- # @return [Symbol] The symbol representation of the class.
91
- def self.resource_class_to_symbol(klass)
92
- klass.to_s.gsub(/(.+::)?(.+)/, "\\2").to_sym
108
+ regexp? ? @match.match(hostname) : (@match == hostname)
93
109
  end
94
110
 
95
- # Converts a symbol to the correspondent DNS resource class.
96
- #
97
- # @param symbol [Symbol] The symbol to convert.
98
- # @param locale [Symbol] The locale to use for the messages.
99
- # @return [Symbol] The class associated to the symbol.
100
- def self.symbol_to_resource_class(symbol, locale = nil)
101
- symbol = symbol.to_s.upcase
111
+ private
102
112
 
103
- begin
104
- "Resolv::DNS::Resource::IN::#{symbol}".constantize
105
- rescue ::NameError
106
- raise(DevDNSd::Errors::InvalidRule.new(Lazier::Localizer.new(:devdnsd, ::File.absolute_path(::Pathname.new(::File.dirname(__FILE__)).to_s + "/../../locales/"), locale).i18n.invalid_class(symbol)))
107
- end
108
- end
113
+ # :nodoc:
114
+ def setup(match, reply, type, options, block)
115
+ @match = match
109
116
 
110
- private
111
- # Setups a new rule.
112
- #
113
- # @param match [String|Regexp] The pattern to match.
114
- # @param reply [String] The IP or hostname to reply back to the client.
115
- # @param type [Symbol] The type of request to match.
116
- # @param options [Hash] A list of options for the request.
117
- # @param block [Proc] An optional block to compute the reply instead of using the `reply` parameter.
118
- def setup(match, reply, type, options, block)
119
- @match = match
117
+ if block.present? # reply acts like a type, type is ignored
118
+ @type = type || :A
119
+ @reply = nil
120
+ else # reply acts like a reply
121
+ @reply = reply || "127.0.0.1"
120
122
  @type = type || :A
121
- @reply = block.blank? ? (reply || "127.0.0.1") : nil
122
- @options = options
123
- @block = block
124
- end
125
-
126
- # Validates a newly created rule.
127
- def validate_rule
128
- raise(DevDNSd::Errors::InvalidRule.new(i18n.rule_invalid_call)) if @reply.blank? && @block.nil?
129
- raise(DevDNSd::Errors::InvalidRule.new(i18n.rule_invalid_options)) if !@options.is_a?(::Hash)
130
123
  end
131
124
 
132
- # Setups a new rule.
133
- #
134
- # @param rv [Rule] The rule that is been created.
135
- # @param reply_or_type [String|Symbol] The IP or hostname to reply back to the client (or the type of request to match, if a block is provided).
136
- # @param type [Symbol] The type of request to match. This is ignored if a block is provided.
137
- # @param options [Hash] A list of options for the request.
138
- # @param block [Proc] An optional block to compute the reply instead of using the `reply_or_type` parameter. In this case `reply_or_type` is used for the type of the request and `type` is ignored.
139
- # @return [Rule] The new rule.
140
- def self.setup(rv, reply_or_type, type, options = {}, block)
141
- rv.options = options
142
- rv.block = block
143
-
144
- if block.present? then # reply_or_type acts like a type, type is ignored
145
- rv.type = reply_or_type || :A
146
- rv.reply = nil
147
- else # reply_or_type acts like a reply
148
- rv.reply = reply_or_type || "127.0.0.1"
149
- rv.type = type || :A
150
- end
151
-
152
- rv
153
- end
125
+ @options = options
126
+ @block = block
127
+ locale = options.is_a?(Hash) ? options[:locale] : :en
128
+ @i18n = Bovem::I18n.new(locale, root: "devdnsd", path: ::Pathname.new(::File.dirname(__FILE__)).to_s + "/../../locales/")
129
+ end
154
130
 
155
- # Validate options for a new rule creation.
156
- #
157
- # @param reply_or_type [String|Symbol] The IP or hostname to reply back to the client (or the type of request to match, if a block is provided).
158
- # @param options [Hash] A list of options for the request.
159
- # @param block [Proc] An optional block to compute the reply instead of using the `reply_or_type` parameter. In this case `reply_or_type` is used for the type of the request and `type` is ignored.
160
- # @param localizer [Localizer] A localizer object.
161
- def self.validate_options(reply_or_type, options, block, localizer)
162
- raise(DevDNSd::Errors::InvalidRule.new(localizer.i18n.rule_invalid_call)) if reply_or_type.blank? && block.nil?
163
- raise(DevDNSd::Errors::InvalidRule.new(localizer.i18n.rule_invalid_options)) if !options.is_a?(::Hash)
164
- end
131
+ # Validates a newly created rule.
132
+ def validate_rule
133
+ raise(DevDNSd::Errors::InvalidRule, @i18n.rule_invalid_call) if @reply.blank? && @block.nil?
134
+ raise(DevDNSd::Errors::InvalidRule, @i18n.rule_invalid_options) unless @options.is_a?(::Hash)
135
+ end
165
136
  end
166
137
  end