dnsomatic 0.2.2 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/dnsomatic +6 -4
- data/lib/dnsomatic.rb +2 -2
- data/lib/dnsomatic/config.rb +92 -46
- data/lib/dnsomatic/opts.rb +1 -1
- data/tests/confs/1_stanza_no_defs_no_u_p.cf +2 -0
- data/tests/confs/3_good_stanzas.cf +9 -0
- data/tests/confs/3_stanzas_no_def_no_u_p.cf +6 -0
- data/tests/confs/defs_with_bad_mx_set.cf +4 -0
- data/tests/confs/defs_with_mx_set.cf +4 -0
- data/tests/confs/defs_with_override_ipfetch.cf +4 -0
- data/tests/confs/empty_conf.cf +0 -0
- data/tests/confs/working_only_defs.cf +3 -0
- data/tests/test_config.rb +78 -0
- data/tests/test_logger.rb +76 -0
- data/tests/test_opts.rb +22 -0
- metadata +13 -2
data/bin/dnsomatic
CHANGED
@@ -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"
|
data/lib/dnsomatic.rb
CHANGED
@@ -106,7 +106,7 @@ require 'open-uri'
|
|
106
106
|
# -h, --help Display this help text
|
107
107
|
|
108
108
|
module DNSOMatic
|
109
|
-
VERSION = '0.
|
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
|
data/lib/dnsomatic/config.rb
CHANGED
@@ -1,49 +1,37 @@
|
|
1
|
-
require '
|
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
|
-
|
24
|
-
|
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
|
-
#
|
41
|
-
#
|
42
|
-
@
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
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::
|
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
|
-
|
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
|
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 |
|
103
|
-
stanza =
|
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[
|
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
|
data/lib/dnsomatic/opts.rb
CHANGED
@@ -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
|
File without changes
|
@@ -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
|
data/tests/test_opts.rb
CHANGED
@@ -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.
|
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-
|
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
|