dnsomatic 0.2.2 → 0.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.
@@ -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