dnsomatic 0.2.2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -34,15 +34,17 @@ begin
34
34
  obj.send( opts.force ? 'update!' : 'update')
35
35
  end
36
36
  end
37
- rescue => e
37
+ rescue SystemExit => e
38
+ # we call this in a few places (mainly from option processing [-h, -V])
39
+ # a no-op for us.
40
+ rescue Exception => e
38
41
  if e.kind_of?(DNSOMatic::Error)
39
- msg = e
42
+ msg = e.message
40
43
  else
41
44
  msg = "Rescued an unhandled exception of type: #{e.class}\n"
45
+ msg += e.message + "\n"
42
46
  end
43
47
 
44
- msg += "The exception contains the following message:\n"
45
- msg += e.message
46
48
 
47
49
  if opts.debug
48
50
  msg += "Backtrace:\n"
@@ -106,7 +106,7 @@ require 'open-uri'
106
106
  # -h, --help Display this help text
107
107
 
108
108
  module DNSOMatic
109
- VERSION = '0.2.2'
109
+ VERSION = '0.4.0'
110
110
  USERAGENT = "Ruby_DNS-o-Matic/#{VERSION}"
111
111
 
112
112
  # We provide our easily distinguishable exception class so that we can easily
@@ -127,7 +127,7 @@ module DNSOMatic
127
127
  rescue OpenURI::HTTPError, SocketError => e
128
128
  msg = "Error communicating with #{uri.host}\n"
129
129
  msg += "Message was: #{e.message}\n"
130
- msg += "Full URL being requested: #{uri}"
130
+ msg += "Full URL being requested: #{uri}\n"
131
131
  raise(DNSOMatic::Error, msg)
132
132
  end
133
133
  end
@@ -1,49 +1,37 @@
1
- require 'net/https'
1
+ require 'resolv'
2
2
 
3
3
  module DNSOMatic
4
4
  # A class to handle 'parsing' the configuration files and setting defaults
5
5
  # as required. Config files are actually YAML files, so the parsing is
6
6
  # offloaded for the most part.
7
7
  class Config
8
- #in most cases, a user can simply set username and password in a defaults:
9
- #stanza and fire the client.
10
- @@defaults = { 'hostname' => 'all.dnsomatic.com',
11
- 'wildcard' => 'NOCHG',
12
- 'mx' => 'NOCHG',
13
- 'backmx' => 'NOCHG',
14
- 'offline' => 'NOCHG',
15
- 'webipfetchurl' => 'http://myip.dnsomatic.com/' }
16
-
17
8
  # Create a new instance with the option of specifying an alternate config
18
9
  # file to read. The default config file is either $HOME/.dnsomatic.cf (on
19
10
  # unix or if Windows specifies a $HOME environment variable) or
20
11
  # %APPDATA%/.dnsomatic.cf for most Windows environments.
21
12
  def initialize(conffile = nil)
22
13
  stdcf = File.join(ENV['HOME'] || ENV['APPDATA'], '.dnsomatic.cf')
23
- if conffile
24
- if File.exists?(conffile)
25
- @cf = conffile
26
- else
27
- raise(DNSOMatic::ConfErr, "Invalid config file: #{conffile}")
28
- end
29
- elsif File.exists?(stdcf)
30
- @cf = stdcf
31
- else
32
- #in this case, the values from the defaults: stanza in the default conf
33
- #will provide all values required.
34
- @cf = nil
35
- end
14
+ @cf = conffile.nil? ? stdcf : conffile
15
+ raise(DNSOMatic::Error, "Invalid config file: #{conffile}") unless File.exists?(@cf)
36
16
 
37
17
  @updaters = nil
38
18
  @config = {}
39
19
 
40
- # the user config must supply values for these, either in a specific
41
- # host updater stanza or by overriding the global default in defaults:
42
- @req_conf = %w(username password)
43
-
44
- msg = @cf.nil? ? "Couldn't find a user config. Using defaults only." : \
45
- "Using config file #{@cf}"
46
- Logger::log(msg)
20
+ #in most cases, a user can simply set username and password in a defaults:
21
+ #stanza and fire the client.
22
+ @defaults = { 'hostname' => 'all.dnsomatic.com',
23
+ 'wildcard' => 'NOCHG',
24
+ 'mx' => 'NOCHG',
25
+ 'backmx' => 'NOCHG',
26
+ 'offline' => 'NOCHG',
27
+ 'webipfetchurl' => 'http://myip.dnsomatic.com/' }
28
+
29
+ @type_validators = { 'hostname' => 'host',
30
+ 'wildcard' => 'on_nochg',
31
+ 'mx' => 'host',
32
+ 'backmx' => 'yes_nochg',
33
+ 'offline' => 'yes_nochg',
34
+ 'webipfetchurl' => 'url' }
47
35
 
48
36
  load()
49
37
  end
@@ -86,37 +74,61 @@ module DNSOMatic
86
74
  end
87
75
 
88
76
  def load
89
- return @@defaults if @cf.nil?
90
-
91
77
  conf = DNSOMatic::yaml_read(@cf)
92
- raise DNSOMatic::ConfErr, "Invalid configuration format in #{@cf}" unless conf.kind_of?(Hash)
78
+ raise DNSOMatic::Error, "Invalid configuration format in #{@cf}" unless conf.kind_of?(Hash)
93
79
 
94
80
  if conf.has_key?('defaults')
95
81
  #allow the user to override our built-in defaults
96
- @@defaults.merge!(conf['defaults'])
82
+ @defaults.merge!(conf['defaults'])
97
83
  #if they've provided only the defaults stanza, we'll use it to perform
98
- #the update, otherwise remove it as it has been folded into @@defaults
84
+ #the update, otherwise remove it as it has been folded into @defaults
99
85
  conf.delete('defaults') if conf.keys.size > 1
100
86
  end
101
87
 
102
- conf.each_key do |token|
103
- stanza = @@defaults.merge(conf[token])
104
- @req_conf.each do |required|
105
- #still test for existence in case the defaults get munged.
106
- if !stanza.has_key?(required) or stanza[required].nil?
107
- msg = "Invalid configuration for Host Updater named '#{token}'\n"
108
- msg += "Please define the field: #{required}."
109
- raise DNSOMatic::ConfErr, msg
110
- end
111
- end
88
+ conf.each_key do |name|
89
+ stanza = @defaults.merge(conf[name])
112
90
 
113
91
  #just in case
114
92
  stanza.each_pair do |k,v|
115
93
  stanza[k] = fmt(v)
116
94
  end
117
95
 
96
+ validate(name, stanza)
97
+
118
98
  #save our merged version in case we're just dump our config to stdout
119
- @config[token] = stanza
99
+ @config[name] = stanza
100
+ end
101
+ end
102
+
103
+ def validate(name, stanza)
104
+ #first, ensure we have the _required_ fields in an update def stanza
105
+ %w(username password).each do |required|
106
+ #still test for existence in case the defaults get munged.
107
+ if !stanza.has_key?(required) or stanza[required].nil?
108
+ msg = "Invalid configuration for Host Updater named '#{name}'\n"
109
+ msg += "Please define the field: #{required}.\n"
110
+ raise(DNSOMatic::Error, msg)
111
+ end
112
+ end
113
+
114
+ @type_validators.each do |field, validator|
115
+ self.send("validate_#{validator}", field, stanza[field])
116
+ end
117
+
118
+ #the dnsomatic api spec indicates that mx/back mx can be either NOCHG
119
+ #or a hostname that must resolve to an IP.
120
+ %w(mx backmx).each do |mxtype|
121
+ mxval = stanza[mxtype]
122
+ next if mxval.eql?('NOCHG')
123
+
124
+ begin
125
+ Resolv.getaddress(mxval)
126
+ rescue Resolv::ResolvError => e
127
+ msg = "Invalid value for #{mxtype}.\n"
128
+ msg += "It must be either NOCHG or a valid hostname.\n"
129
+ msg += e.message + "\n"
130
+ raise(DNSOMatic::Error, msg)
131
+ end
120
132
  end
121
133
  end
122
134
 
@@ -138,5 +150,39 @@ module DNSOMatic
138
150
  val.to_s.gsub(/\s+/, '')
139
151
  end
140
152
  end
153
+
154
+ def validate_host(field, val)
155
+ return true if val.eql?('NOCHG')
156
+ return true if val.match(/[^\s]+\.[^\s]+/) #no great, but workable
157
+ msg = "Invalid hostname defined for #{field}.\n"
158
+ msg += "You gave: #{val}\n"
159
+ raise(DNSOMatic::Error, msg)
160
+ end
161
+
162
+ def validate_yes_nochg(field, val)
163
+ valid = %w(YES NO NOCHG)
164
+ return true if valid.include?(val.upcase)
165
+ msg = "Invalid value for #{field}.\n"
166
+ msg += "It should be one of: #{valid.join(', ')}\n"
167
+ msg += "You gave: #{val}\n"
168
+ raise(DNSOMatic::Error, msg)
169
+ end
170
+
171
+ def validate_on_nochg(field, val)
172
+ valid = %w(ON OFF NOCHG)
173
+ return true if valid.include?(val.upcase)
174
+ msg = "Invalid value for #{field}.\n"
175
+ msg += "It should be one of: #{valid.join(', ')}\n"
176
+ msg += "You gave: #{val}\n"
177
+ raise(DNSOMatic::Error, msg)
178
+ end
179
+
180
+ def validate_url(field, val)
181
+ return true if val.match('http.//.*')
182
+ msg = "Invalid value for #{field}.\n"
183
+ msg += "It should be on http(s)-style URL.\n"
184
+ msg += "You gave: #{val}\n"
185
+ raise(DNSOMatic::Error, msg)
186
+ end
141
187
  end
142
188
  end
@@ -84,7 +84,7 @@ module DNSOMatic
84
84
  rescue OptionParser::ParseError => e
85
85
  msg = "Extra/Unknown arguments used:\n"
86
86
  msg += "\t#{e.message}\n"
87
- msg += "Remaining args: #{args.join(', ')}" if args.size > 0
87
+ msg += "Remaining args: #{args.join(', ')}\n" if args.size > 0
88
88
  raise(DNSOMatic::Error, msg)
89
89
  end
90
90
  end
@@ -0,0 +1,2 @@
1
+ stanza1:
2
+ hostname: blah.example.com
@@ -0,0 +1,9 @@
1
+ defaults:
2
+ username: myuser
3
+ password: mypass
4
+ stanza1:
5
+ hostname: foo.bar.baz
6
+ stanza2:
7
+ webipfetchurl: http://somesite.com/whatismyip.php
8
+ stanza3:
9
+ hostname: baz.bar.foo
@@ -0,0 +1,6 @@
1
+ stanza1:
2
+ hostname: foo.bar.baz
3
+ stanza2:
4
+ webipfetchurl: http://somesite.com/whatismyip.php
5
+ stanza3:
6
+ hostname: baz.bar.foo
@@ -0,0 +1,4 @@
1
+ defaults:
2
+ username: blah
3
+ password: blah
4
+ mx: blah.example.com
@@ -0,0 +1,4 @@
1
+ defaults:
2
+ username: blah
3
+ password: blah
4
+ mx: example.com
@@ -0,0 +1,4 @@
1
+ stanza1:
2
+ username: foo
3
+ password: bar
4
+ webipfetchurl: http://example.com
File without changes
@@ -0,0 +1,3 @@
1
+ defaults:
2
+ username: myuser
3
+ password: mypass
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/ruby -w
2
+ $: << '../lib'
3
+
4
+ require 'test/unit'
5
+
6
+ require 'dnsomatic'
7
+
8
+ class TestConfig < Test::Unit::TestCase
9
+ def setup
10
+ $cfd = File.join(Dir.pwd, 'confs')
11
+ $orig_home = ENV['HOME']
12
+ end
13
+
14
+ def test_no_exception_with_basic_default_conf
15
+ assert_nothing_raised { DNSOMatic::Config.new }
16
+ end
17
+
18
+ def test_exception_on_no_default_config_file
19
+ ENV['HOME'] = '/tmp'
20
+ assert_raises(DNSOMatic::Error) { DNSOMatic::Config.new }
21
+ end
22
+
23
+ def test_non_existent_conf_raises_exception
24
+ conf = File.join($cfd, 'bad_conf_file_name.cf')
25
+ assert_raises(DNSOMatic::Error) { DNSOMatic::Config.new(conf) }
26
+ end
27
+
28
+ def test_empty_conf_raises_exception
29
+ conf = File.join($cfd, 'empty_conf.cf')
30
+ assert_raises(DNSOMatic::Error) { DNSOMatic::Config.new(conf) }
31
+ end
32
+
33
+ def test_no_exception_raised_for_basic_conf
34
+ conf = File.join($cfd, 'working_only_defs.cf')
35
+ assert_nothing_raised { DNSOMatic::Config.new(conf) }
36
+ end
37
+
38
+ def test_no_exception_raised_for_conf_with_multi_stanzas
39
+ conf = File.join($cfd, '3_good_stanzas.cf')
40
+ assert_nothing_raised { DNSOMatic::Config.new(conf) }
41
+ end
42
+
43
+ def test_1_def_and_3_alternates_gives_3_updaters
44
+ conf = File.join($cfd, '3_good_stanzas.cf')
45
+ c = DNSOMatic::Config.new(conf)
46
+ assert_equal(3, c.updaters.length)
47
+ end
48
+
49
+ def test_3_stanzas_no_def_no_u_p_raises
50
+ conf = File.join($cfd, '3_stanzas_no_def_no_u_p.cf')
51
+ assert_raises(DNSOMatic::Error) { DNSOMatic::Config.new(conf) }
52
+ end
53
+
54
+ def test_no_defs_no_u_p_raises
55
+ conf = File.join($cfd, '1_stanza_no_defs_no_u_p.cf')
56
+ assert_raises(DNSOMatic::Error) { DNSOMatic::Config.new(conf) }
57
+ end
58
+
59
+ def test_override_webipfetchurl_works
60
+ conf = File.join($cfd, 'defs_with_override_ipfetch.cf')
61
+ c = DNSOMatic::Config.new(conf)
62
+ #so we know our match below isn't hitting on another stanza
63
+ assert_equal(1, c.updaters.length)
64
+ assert_match(/.*webipfetchurl: http:\/\/example.com.*/m, c.merged_config)
65
+ end
66
+
67
+ def test_mx_rejects_improper_values
68
+ #either NOCHG or a name that resolves to an IP
69
+ conf = File.join($cfd, 'defs_with_mx_set.cf')
70
+ assert_nothing_raised { DNSOMatic::Config.new(conf) }
71
+ conf = File.join($cfd, 'defs_with_bad_mx_set.cf')
72
+ assert_raises(DNSOMatic::Error) { DNSOMatic::Config.new(conf) }
73
+ end
74
+
75
+ def teardown
76
+ ENV['HOME'] = $orig_home
77
+ end
78
+ end
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/ruby -w
2
+ $: << '../lib'
3
+
4
+ require 'test/unit'
5
+ require 'stringio'
6
+
7
+ require 'dnsomatic'
8
+
9
+ class TestLogger < Test::Unit::TestCase
10
+ def setup
11
+ $opts = DNSOMatic::Opts.instance
12
+ $stdout = StringIO.new('')
13
+ $opts.parse([])
14
+ end
15
+
16
+ def test_nothing_logged_if_not_verbose
17
+ DNSOMatic::Logger.log('nothing logged')
18
+ assert_equal('', $stdout.string)
19
+ end
20
+
21
+ def test_log_matches_when_verbose
22
+ $opts.parse(%w(-v)) #enable verbose so that logs are generated
23
+ DNSOMatic::Logger.log('logged')
24
+ assert_equal("logged\n", $stdout.string)
25
+ end
26
+
27
+ def test_nothing_alerted_without_verb_or_alert
28
+ DNSOMatic::Logger.alert('nothing logged')
29
+ assert_equal('', $stdout.string)
30
+ end
31
+
32
+ def test_alert_with_alert_opt
33
+ $opts.parse(%w(-a))
34
+ DNSOMatic::Logger.alert('something logged')
35
+ assert_equal("something logged\n", $stdout.string)
36
+ end
37
+
38
+ def test_alert_with_verbose_opt
39
+ $opts.parse(%w(-v))
40
+ DNSOMatic::Logger.alert('something logged')
41
+ assert_equal("something logged\n", $stdout.string)
42
+ end
43
+
44
+ def test_alert_with_verbose_and_alert_opts
45
+ $opts.parse(%w(-v -a))
46
+ DNSOMatic::Logger.alert('something logged')
47
+ assert_equal("something logged\n", $stdout.string)
48
+ end
49
+
50
+ def test_warn_without_verbose_or_alert
51
+ DNSOMatic::Logger.warn('something logged')
52
+ assert_equal("something logged\n", $stdout.string)
53
+ end
54
+
55
+ def test_warn_with_verbose
56
+ $opts.parse(%w(-v))
57
+ DNSOMatic::Logger.warn('something logged')
58
+ assert_equal("something logged\n", $stdout.string)
59
+ end
60
+
61
+ def test_warn_with_alert
62
+ $opts.parse(%w(-a))
63
+ DNSOMatic::Logger.warn('something logged')
64
+ assert_equal("something logged\n", $stdout.string)
65
+ end
66
+
67
+ def test_warn_with_alert_and_verbose
68
+ $opts.parse(%w(-a -v))
69
+ DNSOMatic::Logger.warn('something logged')
70
+ assert_equal("something logged\n", $stdout.string)
71
+ end
72
+
73
+ def teardown
74
+ $stdout.truncate(0)
75
+ end
76
+ end
@@ -9,6 +9,24 @@ require 'dnsomatic'
9
9
  class TestOpts < Test::Unit::TestCase
10
10
  def setup
11
11
  $opts = DNSOMatic::Opts.instance
12
+ $stdout = StringIO.new('')
13
+ $opts.parse([])
14
+ end
15
+
16
+ def test_version_request_exits
17
+ assert_raises(SystemExit) { $opts.parse(%w(-V)) }
18
+ end
19
+
20
+ def test_version_number_sane
21
+ begin
22
+ $opts.parse(%w(-V))
23
+ rescue SystemExit
24
+ assert_equal("#{DNSOMatic::VERSION}\n", $stdout.string)
25
+ end
26
+ end
27
+
28
+ def test_help_exits
29
+ assert_raises(SystemExit) { $opts.parse(%w(-h)) }
12
30
  end
13
31
 
14
32
  def test_set_verbose
@@ -74,4 +92,8 @@ class TestOpts < Test::Unit::TestCase
74
92
  def test_extra_args_raise_exception
75
93
  assert_raise(DNSOMatic::Error) { $opts.parse(%w(somearg)) }
76
94
  end
95
+
96
+ def teardown
97
+ $stdout.truncate(0)
98
+ end
77
99
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dnsomatic
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Walton
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-08-27 00:00:00 -04:00
12
+ date: 2008-09-01 00:00:00 -04:00
13
13
  default_executable: dnsomatic
14
14
  dependencies: []
15
15
 
@@ -32,8 +32,19 @@ files:
32
32
  - lib/dnsomatic.rb
33
33
  - tests/test_ipstatus.rb
34
34
  - tests/test_opts.rb
35
+ - tests/test_logger.rb
35
36
  - tests/test_updater.rb
36
37
  - tests/all_tests.rb
38
+ - tests/confs
39
+ - tests/confs/1_stanza_no_defs_no_u_p.cf
40
+ - tests/confs/working_only_defs.cf
41
+ - tests/confs/defs_with_mx_set.cf
42
+ - tests/confs/3_good_stanzas.cf
43
+ - tests/confs/empty_conf.cf
44
+ - tests/confs/defs_with_override_ipfetch.cf
45
+ - tests/confs/defs_with_bad_mx_set.cf
46
+ - tests/confs/3_stanzas_no_def_no_u_p.cf
47
+ - tests/test_config.rb
37
48
  - tests/test_iplookup.rb
38
49
  has_rdoc: true
39
50
  homepage: http://rubyforge.org/projects/dnsomatic