cloud-dyndns 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +94 -0
- data/Guardfile +24 -0
- data/LICENSE.txt +22 -0
- data/README.md +101 -0
- data/Rakefile +9 -0
- data/bin/cloud-dyndns +9 -0
- data/cloud-dyndns.gemspec +34 -0
- data/cloud-dyndns.rb +12 -0
- data/doc/crontab_example +2 -0
- data/lib/config.rb +68 -0
- data/lib/updater.rb +169 -0
- data/lib/version.rb +3 -0
- data/spec/config_spec.rb +83 -0
- data/spec/fixtures/config.yml +17 -0
- data/spec/test_helper.rb +13 -0
- data/spec/updater_spec.rb +313 -0
- metadata +163 -0
data/Gemfile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
source 'https://rubygems.org/'
|
2
|
+
|
3
|
+
gemspec
|
4
|
+
|
5
|
+
group :test do
|
6
|
+
gem 'minitest-reporters'
|
7
|
+
gem 'minitest-matchers'
|
8
|
+
gem 'minitest'
|
9
|
+
gem 'mocha'
|
10
|
+
gem 'minitest-spec-expect'
|
11
|
+
end
|
12
|
+
|
13
|
+
group :development do
|
14
|
+
gem 'guard'
|
15
|
+
gem 'guard-minitest'
|
16
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
cloud-dyndns (0.0.1)
|
5
|
+
fog (= 1.18.0)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
ansi (1.4.3)
|
11
|
+
builder (3.2.2)
|
12
|
+
celluloid (0.15.2)
|
13
|
+
timers (~> 1.1.0)
|
14
|
+
coderay (1.1.0)
|
15
|
+
excon (0.28.0)
|
16
|
+
ffi (1.9.3)
|
17
|
+
fog (1.18.0)
|
18
|
+
builder
|
19
|
+
excon (~> 0.28.0)
|
20
|
+
formatador (~> 0.2.0)
|
21
|
+
mime-types
|
22
|
+
multi_json (~> 1.0)
|
23
|
+
net-scp (~> 1.1)
|
24
|
+
net-ssh (>= 2.1.3)
|
25
|
+
nokogiri (~> 1.5)
|
26
|
+
ruby-hmac
|
27
|
+
formatador (0.2.4)
|
28
|
+
guard (2.2.4)
|
29
|
+
formatador (>= 0.2.4)
|
30
|
+
listen (~> 2.1)
|
31
|
+
lumberjack (~> 1.0)
|
32
|
+
pry (>= 0.9.12)
|
33
|
+
thor (>= 0.18.1)
|
34
|
+
guard-minitest (2.1.1)
|
35
|
+
guard (~> 2.0)
|
36
|
+
minitest (>= 3.0)
|
37
|
+
hashie (2.0.5)
|
38
|
+
listen (2.2.0)
|
39
|
+
celluloid (>= 0.15.2)
|
40
|
+
rb-fsevent (>= 0.9.3)
|
41
|
+
rb-inotify (>= 0.9)
|
42
|
+
lumberjack (1.0.4)
|
43
|
+
metaclass (0.0.1)
|
44
|
+
method_source (0.8.2)
|
45
|
+
mime-types (2.0)
|
46
|
+
mini_portile (0.5.2)
|
47
|
+
minitest (4.7.5)
|
48
|
+
minitest-matchers (1.3.0)
|
49
|
+
minitest (~> 4.7)
|
50
|
+
minitest-reporters (0.14.23)
|
51
|
+
ansi
|
52
|
+
builder
|
53
|
+
minitest (>= 2.12, < 5.0)
|
54
|
+
powerbar
|
55
|
+
minitest-spec-expect (0.1.1)
|
56
|
+
minitest (>= 2.4)
|
57
|
+
mocha (0.14.0)
|
58
|
+
metaclass (~> 0.0.1)
|
59
|
+
multi_json (1.8.2)
|
60
|
+
net-scp (1.1.2)
|
61
|
+
net-ssh (>= 2.6.5)
|
62
|
+
net-ssh (2.7.0)
|
63
|
+
nokogiri (1.6.0)
|
64
|
+
mini_portile (~> 0.5.0)
|
65
|
+
powerbar (1.0.11)
|
66
|
+
ansi (~> 1.4.0)
|
67
|
+
hashie (>= 1.1.0)
|
68
|
+
pry (0.9.12.3)
|
69
|
+
coderay (~> 1.0)
|
70
|
+
method_source (~> 0.8)
|
71
|
+
slop (~> 3.4)
|
72
|
+
rake (10.1.0)
|
73
|
+
rb-fsevent (0.9.3)
|
74
|
+
rb-inotify (0.9.2)
|
75
|
+
ffi (>= 0.5.0)
|
76
|
+
ruby-hmac (0.4.0)
|
77
|
+
slop (3.4.7)
|
78
|
+
thor (0.18.1)
|
79
|
+
timers (1.1.0)
|
80
|
+
|
81
|
+
PLATFORMS
|
82
|
+
ruby
|
83
|
+
|
84
|
+
DEPENDENCIES
|
85
|
+
bundler (~> 1.3)
|
86
|
+
cloud-dyndns!
|
87
|
+
guard
|
88
|
+
guard-minitest
|
89
|
+
minitest
|
90
|
+
minitest-matchers
|
91
|
+
minitest-reporters
|
92
|
+
minitest-spec-expect
|
93
|
+
mocha
|
94
|
+
rake
|
data/Guardfile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard :minitest do
|
5
|
+
|
6
|
+
# with Minitest::Spec
|
7
|
+
watch(%r{^spec/(.*)_spec\.rb})
|
8
|
+
watch(%r{^lib/(.+)\.rb}) { |m| "spec/#{m[1]}_spec.rb" }
|
9
|
+
watch(%r{^spec/spec_helper\.rb}) { 'spec' }
|
10
|
+
|
11
|
+
# Rails 4
|
12
|
+
# watch(%r{^app/(.+)\.rb}) { |m| "test/#{m[1]}_test.rb" }
|
13
|
+
# watch(%r{^app/controllers/application_controller\.rb}) { 'test/controllers' }
|
14
|
+
# watch(%r{^app/controllers/(.+)_controller\.rb}) { |m| "test/integration/#{m[1]}_test.rb" }
|
15
|
+
# watch(%r{^app/views/(.+)_mailer/.+}) { |m| "test/mailers/#{m[1]}_mailer_test.rb" }
|
16
|
+
# watch(%r{^lib/(.+)\.rb}) { |m| "test/lib/#{m[1]}_test.rb" }
|
17
|
+
# watch(%r{^test/.+_test\.rb})
|
18
|
+
# watch(%r{^test/test_helper\.rb}) { 'test' }
|
19
|
+
|
20
|
+
# Rails < 4
|
21
|
+
# watch(%r{^app/controllers/(.*)\.rb}) { |m| "test/functional/#{m[1]}_test.rb" }
|
22
|
+
# watch(%r{^app/helpers/(.*)\.rb}) { |m| "test/helpers/#{m[1]}_test.rb" }
|
23
|
+
# watch(%r{^app/models/(.*)\.rb}) { |m| "test/unit/#{m[1]}_test.rb" }
|
24
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 nat
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
# cloud-dyndns
|
2
|
+
|
3
|
+
Use a cloud DNS provider like a dynamic dns service. A Ruby executable to
|
4
|
+
stick in a crontab. If the excellent [fog](http://fog.io/) supports the
|
5
|
+
service, cloud-dyndns will (probably) work for you.
|
6
|
+
|
7
|
+
# install
|
8
|
+
|
9
|
+
```
|
10
|
+
gem install cloud-dyndns
|
11
|
+
```
|
12
|
+
|
13
|
+
# usage
|
14
|
+
|
15
|
+
```
|
16
|
+
cloud-dyndns --config ~/$your_yaml_file
|
17
|
+
```
|
18
|
+
|
19
|
+
where `$your_yaml_file` is the path to a file that looks like:
|
20
|
+
|
21
|
+
# config example
|
22
|
+
|
23
|
+
```yaml
|
24
|
+
:credentials:
|
25
|
+
:provider: 'AWS'
|
26
|
+
:aws_secret_access_key: 'your-secretkeykeykey'
|
27
|
+
:aws_access_key_id: 'your-access-id'
|
28
|
+
:zones:
|
29
|
+
- :domain: "looting.biz"
|
30
|
+
:targets:
|
31
|
+
- "phl.looting.biz"
|
32
|
+
- "*.phl.looting.biz"
|
33
|
+
- :domain: "narf.io"
|
34
|
+
:targets:
|
35
|
+
- "phl.narf.io"
|
36
|
+
- "*.phl.narf.io"
|
37
|
+
```
|
38
|
+
|
39
|
+
# command line options
|
40
|
+
|
41
|
+
there's -c/--config and -l/--log:
|
42
|
+
|
43
|
+
* --config is mandatory: a path to your config file
|
44
|
+
* --log is optional: if not given it will log to stdout. if given a path it
|
45
|
+
will log to that file.
|
46
|
+
|
47
|
+
# crontab example
|
48
|
+
|
49
|
+
```
|
50
|
+
*/15 * * * * cloud-dyndns --config /Users/nat/.cloud-dyndns.yml > ~/.cdyndns.log
|
51
|
+
```
|
52
|
+
|
53
|
+
will make sure your domain is up to date every 15 minutes, and will log to
|
54
|
+
`~/.cdyndns.log`
|
55
|
+
|
56
|
+
If you use rbenv, rvm, or rb-john-wanamaker...I'm not sure what you can do. I
|
57
|
+
vaguely remember with rvm you could create "wrappers". I know it's possible to
|
58
|
+
use software the way you might want. Sorry I'm not more helpful here.
|
59
|
+
|
60
|
+
# other
|
61
|
+
|
62
|
+
I refactored this and added tests for release. I've been using it for about a
|
63
|
+
year. [Fog](http://fog.io/) is good, contributions welcome.
|
64
|
+
|
65
|
+
# testing
|
66
|
+
|
67
|
+
```
|
68
|
+
bundle install --without development
|
69
|
+
```
|
70
|
+
|
71
|
+
```
|
72
|
+
rake test
|
73
|
+
```
|
74
|
+
|
75
|
+
This will allow running tests on ruby >= 1.9.2
|
76
|
+
|
77
|
+
# development
|
78
|
+
|
79
|
+
```
|
80
|
+
bundle install --without nothing
|
81
|
+
```
|
82
|
+
|
83
|
+
(See [this SO
|
84
|
+
post](http://stackoverflow.com/questions/4118055/rails-bundler-doesnt-install-gems-inside-a-group)
|
85
|
+
about why I suggest just throwing that `--without nothing` in there.
|
86
|
+
|
87
|
+
```
|
88
|
+
bundle exec guard
|
89
|
+
```
|
90
|
+
|
91
|
+
Development requires Ruby >= 1.9.3 due to the listen gem's requirement of Ruby
|
92
|
+
1.9.3.
|
93
|
+
|
94
|
+
# todo
|
95
|
+
|
96
|
+
* allow setting of hostmaster email?
|
97
|
+
* allow usage of a different wtfismyip-type service?
|
98
|
+
* use a version of fog that requires only 1.8.7?
|
99
|
+
* this would include adding a fix for [this
|
100
|
+
issue](https://github.com/fog/fog/issues/1093) in this library.
|
101
|
+
|
data/Rakefile
ADDED
data/bin/cloud-dyndns
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'version'
|
5
|
+
|
6
|
+
description = %q{
|
7
|
+
update DNS entries on cloud providers (Route 53 et al) based on your current external IP address. think like a dyndns updater. no-ip, et al.
|
8
|
+
}
|
9
|
+
|
10
|
+
Gem::Specification.new do |spec|
|
11
|
+
spec.name = "cloud-dyndns"
|
12
|
+
spec.version = CloudDyndns::VERSION
|
13
|
+
spec.authors = ["Nat Lownes"]
|
14
|
+
spec.email = ["nat.lownes@gmail.com"]
|
15
|
+
spec.description = description
|
16
|
+
spec.summary = description
|
17
|
+
spec.homepage = "https://github.com/natlownes/cloud-dyndns"
|
18
|
+
spec.license = "MIT"
|
19
|
+
|
20
|
+
spec.files = `git ls-files`.split($/)
|
21
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
22
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
23
|
+
spec.require_paths = ["lib"]
|
24
|
+
|
25
|
+
spec.add_dependency "fog", '=1.18.0'
|
26
|
+
|
27
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
28
|
+
spec.add_development_dependency "rake"
|
29
|
+
spec.add_development_dependency "minitest-reporters"
|
30
|
+
spec.add_development_dependency "minitest-matchers"
|
31
|
+
spec.add_development_dependency "minitest"
|
32
|
+
spec.add_development_dependency "mocha"
|
33
|
+
spec.add_development_dependency "minitest-spec-expect"
|
34
|
+
end
|
data/cloud-dyndns.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
|
4
|
+
module CloudDyndns
|
5
|
+
ROOTDIR=File.expand_path(File.join(File.dirname(__FILE__)))
|
6
|
+
LIBDIR=File.join(ROOTDIR, 'lib')
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
require File.join CloudDyndns::LIBDIR, 'version'
|
11
|
+
require File.join CloudDyndns::LIBDIR, 'config'
|
12
|
+
require File.join CloudDyndns::LIBDIR, 'updater'
|
data/doc/crontab_example
ADDED
data/lib/config.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'logger'
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
|
6
|
+
module CloudDyndns
|
7
|
+
|
8
|
+
class Config
|
9
|
+
|
10
|
+
def usage_banner
|
11
|
+
<<-EOF
|
12
|
+
USAGE: cloud-dyndns --config [/path/to/config.yaml] --log [/path/to/log]
|
13
|
+
EOF
|
14
|
+
end
|
15
|
+
|
16
|
+
def config_instructions
|
17
|
+
'Path to config file (required)'
|
18
|
+
end
|
19
|
+
|
20
|
+
def log_instructions
|
21
|
+
'Path to log file (if not given, will write to stdout)'
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(args)
|
25
|
+
check_args(args)
|
26
|
+
|
27
|
+
parser = OptionParser.new do |opts|
|
28
|
+
opts.banner = usage_banner
|
29
|
+
|
30
|
+
opts.on '-c', '--config PATH', config_instructions do |v|
|
31
|
+
set_config!(v)
|
32
|
+
end
|
33
|
+
|
34
|
+
opts.on '-l', '--log [PATH]', log_instructions do |v|
|
35
|
+
set_log!(v)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
parser.parse!(args)
|
40
|
+
end
|
41
|
+
|
42
|
+
def log
|
43
|
+
@log ||= ::Logger.new(@log_path || STDOUT)
|
44
|
+
end
|
45
|
+
|
46
|
+
def config
|
47
|
+
@config ||= ::YAML.load_file(@config_path)
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def check_args(args)
|
53
|
+
if !args or args.length == 0
|
54
|
+
puts usage_banner
|
55
|
+
exit(1)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def set_config!(path)
|
60
|
+
@config_path = File.expand_path(path) if path
|
61
|
+
end
|
62
|
+
|
63
|
+
def set_log!(path)
|
64
|
+
@log_path = path
|
65
|
+
::Kernel.at_exit { @log.close() if @log }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/updater.rb
ADDED
@@ -0,0 +1,169 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
require 'fog'
|
3
|
+
|
4
|
+
|
5
|
+
class CloudDyndns::Updater
|
6
|
+
class NoIPAddressError < StandardError
|
7
|
+
def message
|
8
|
+
"could not get your current ip address: \n
|
9
|
+
cowardly refusing to update your DNS record to nothing"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class NoTargetsSpecified < StandardError
|
14
|
+
def message
|
15
|
+
"no domain names to update were specified"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.domain_name_to_provider_name(name)
|
20
|
+
"#{name}."
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(config_object, logger)
|
24
|
+
@credentials = config_object[:credentials]
|
25
|
+
@log = logger
|
26
|
+
@zone_configs = config_object[:zones]
|
27
|
+
end
|
28
|
+
|
29
|
+
def update!
|
30
|
+
updates = []
|
31
|
+
zone_configs.each do |zone_config|
|
32
|
+
targets = zone_config[:targets]
|
33
|
+
top_level_zone = zone_config[:domain]
|
34
|
+
|
35
|
+
check_config_targets(top_level_zone, targets)
|
36
|
+
|
37
|
+
zone = find_zone_by_name(top_level_zone)
|
38
|
+
|
39
|
+
targets.each do |target|
|
40
|
+
updates << update_record_for_zone(zone, target, zone_config)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
updates = []
|
45
|
+
end
|
46
|
+
|
47
|
+
def zones
|
48
|
+
dns.zones
|
49
|
+
end
|
50
|
+
|
51
|
+
def dns
|
52
|
+
@dns ||= create_dns(@credentials)
|
53
|
+
end
|
54
|
+
|
55
|
+
def log
|
56
|
+
@log
|
57
|
+
end
|
58
|
+
|
59
|
+
def zone_configs
|
60
|
+
@zone_configs || []
|
61
|
+
end
|
62
|
+
|
63
|
+
def find_zone_by_name(zone_domain)
|
64
|
+
find_zone_by_domain(zone_domain) or create_zone_by_domain(zone_domain)
|
65
|
+
end
|
66
|
+
|
67
|
+
def find_zone_by_domain(domain_name)
|
68
|
+
zones.find do |z|
|
69
|
+
z.domain.match(%r{#{domain_name}\.$})
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def create_zone_by_domain(domain_name)
|
74
|
+
zones.create({
|
75
|
+
:domain => domain_name,
|
76
|
+
:email => "hostmaster@#{domain_name}"
|
77
|
+
})
|
78
|
+
end
|
79
|
+
|
80
|
+
def records_for_zone(zone)
|
81
|
+
zone.records.reload
|
82
|
+
end
|
83
|
+
|
84
|
+
def find_record_for_zone_by_name(zone, name)
|
85
|
+
records_for_zone(zone).find do |r|
|
86
|
+
r.name == self.class.domain_name_to_provider_name(name)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def update_record_for_zone(zone, target, zone_config={})
|
91
|
+
# zone is the zone object from Fog
|
92
|
+
# target is a string, like "*.example.com"
|
93
|
+
# or just name.example.com" or just
|
94
|
+
# "example.com"
|
95
|
+
# zone_config is the object from the zones array
|
96
|
+
# in the yaml config
|
97
|
+
current_ip = get_ip_address()
|
98
|
+
|
99
|
+
if current_ip.empty?
|
100
|
+
raise NoIPAddressError.new
|
101
|
+
end
|
102
|
+
|
103
|
+
record = find_record_for_zone_by_name(zone, target)
|
104
|
+
|
105
|
+
target_attributes = {
|
106
|
+
:name => target,
|
107
|
+
:value => current_ip,
|
108
|
+
:ttl => zone_config[:ttl]
|
109
|
+
}
|
110
|
+
|
111
|
+
if is_record_create?(record)
|
112
|
+
return create_record_for_zone(zone, target_attributes)
|
113
|
+
end
|
114
|
+
|
115
|
+
if is_record_update?(record)
|
116
|
+
record.destroy
|
117
|
+
return create_record_for_zone(zone, target_attributes)
|
118
|
+
end
|
119
|
+
|
120
|
+
record
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
|
125
|
+
def check_config_targets(top_level_zone, targets)
|
126
|
+
if !targets or targets.empty?
|
127
|
+
@log << %{
|
128
|
+
#{top_level_zone} has zero targets in config, add some domains to update in your config file
|
129
|
+
}
|
130
|
+
raise NoTargetsSpecified.new
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def create_dns(credentials)
|
135
|
+
Fog::DNS.new(credentials)
|
136
|
+
end
|
137
|
+
|
138
|
+
def default_ttl
|
139
|
+
300
|
140
|
+
end
|
141
|
+
|
142
|
+
def get_ip_address
|
143
|
+
ip_url = "http://wtfismyip.com/text"
|
144
|
+
|
145
|
+
@external_ip ||= OpenURI.open_uri(ip_url).read.strip
|
146
|
+
end
|
147
|
+
|
148
|
+
def is_record_create?(record)
|
149
|
+
!record
|
150
|
+
end
|
151
|
+
|
152
|
+
def is_record_update?(record)
|
153
|
+
# record exists and is not our external ip
|
154
|
+
external_ip = get_ip_address()
|
155
|
+
record && !record.value.include?(external_ip)
|
156
|
+
end
|
157
|
+
|
158
|
+
def create_record_for_zone(zone, attrs={})
|
159
|
+
attributes = {
|
160
|
+
:ttl => (attrs[:ttl] || default_ttl()),
|
161
|
+
:value => attrs[:value],
|
162
|
+
:name => attrs[:name],
|
163
|
+
:type => 'A'
|
164
|
+
}
|
165
|
+
zone.records.create(attributes)
|
166
|
+
@log << "created record: #{attributes.to_yaml}"
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|
data/lib/version.rb
ADDED
data/spec/config_spec.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/test_helper')
|
2
|
+
root = CloudDyndns::ROOTDIR
|
3
|
+
|
4
|
+
config_file_path = File.join(root, 'spec', 'fixtures', 'config.yml')
|
5
|
+
test_log_path = File.join(root, 'spec', 'fixtures', 'test.log')
|
6
|
+
cloud_dyndyns_path = File.join(root, 'bin', 'cloud-dyndns')
|
7
|
+
|
8
|
+
|
9
|
+
describe 'option parsing' do
|
10
|
+
|
11
|
+
describe 'with no options' do
|
12
|
+
it 'should exit with exit code 1' do
|
13
|
+
exited_status_zero = system(cloud_dyndyns_path)
|
14
|
+
|
15
|
+
expect(exited_status_zero).to_equal false
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'should print the usage banner to stdout' do
|
19
|
+
out = `#{cloud_dyndyns_path}`
|
20
|
+
|
21
|
+
expect(out).to_match(/USAGE: cloud-dyndns/)
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
describe 'with invalid --config' do
|
27
|
+
|
28
|
+
it 'should raise MissingArgument' do
|
29
|
+
args = ['--config']
|
30
|
+
|
31
|
+
expect { CloudDyndns::Config.new(args) }.
|
32
|
+
to_raise OptionParser::MissingArgument
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
describe 'with valid options' do
|
38
|
+
after do
|
39
|
+
FileUtils.rm_rf(test_log_path)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should read the config file' do
|
43
|
+
args = ['--config', config_file_path]
|
44
|
+
config = CloudDyndns::Config.new(args)
|
45
|
+
|
46
|
+
expect(config.config[:credentials][:provider]).to_equal 'AWS'
|
47
|
+
expect(config.config[:zones][0][:domain]).to_equal 'looting.biz'
|
48
|
+
expect(config.config[:zones][1][:domain]).to_equal 'narf.io'
|
49
|
+
|
50
|
+
expect(
|
51
|
+
config.config[:credentials][:aws_secret_access_key]
|
52
|
+
).to_equal 'honk-secret'
|
53
|
+
|
54
|
+
expect(
|
55
|
+
config.config[:credentials][:aws_access_key_id]
|
56
|
+
).to_equal 'honk-key'
|
57
|
+
end
|
58
|
+
|
59
|
+
describe 'with no log option' do
|
60
|
+
it 'should set a log to stdout' do
|
61
|
+
args = ['--config', config_file_path]
|
62
|
+
config = CloudDyndns::Config.new(args)
|
63
|
+
|
64
|
+
logdev = config.log.instance_variable_get :@logdev
|
65
|
+
|
66
|
+
expect(logdev.dev).to_equal $stdout
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe 'with a log option' do
|
71
|
+
it 'should create a file at that path' do
|
72
|
+
args = ['--config', config_file_path, '--log', test_log_path]
|
73
|
+
config = CloudDyndns::Config.new(args)
|
74
|
+
|
75
|
+
logdev = config.log.instance_variable_get :@logdev
|
76
|
+
|
77
|
+
expect(logdev.filename).to_equal test_log_path
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
end #valid options
|
82
|
+
end
|
83
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
:credentials:
|
2
|
+
:provider: 'AWS'
|
3
|
+
:aws_secret_access_key: 'honk-secret'
|
4
|
+
:aws_access_key_id: 'honk-key'
|
5
|
+
:zones:
|
6
|
+
- :domain: "looting.biz"
|
7
|
+
:targets:
|
8
|
+
- "phl.looting.biz"
|
9
|
+
- "*.phl.looting.biz"
|
10
|
+
:ttl: 600
|
11
|
+
- :domain: "narf.io"
|
12
|
+
:targets:
|
13
|
+
- "phl.narf.io"
|
14
|
+
- "*.phl.narf.io"
|
15
|
+
|
16
|
+
|
17
|
+
|
data/spec/test_helper.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'minitest/autorun'
|
5
|
+
require 'minitest/mock'
|
6
|
+
require 'minitest/reporters'
|
7
|
+
require 'minitest/matchers'
|
8
|
+
require 'minitest/spec/expect'
|
9
|
+
require 'mocha/setup'
|
10
|
+
|
11
|
+
require File.join(File.dirname(__FILE__), '..', 'cloud-dyndns')
|
12
|
+
|
13
|
+
Minitest::Reporters.use! [Minitest::Reporters::SpecReporter.new]
|
@@ -0,0 +1,313 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/test_helper')
|
3
|
+
root = CloudDyndns::ROOTDIR
|
4
|
+
|
5
|
+
config_file_path = File.join(root, 'spec', 'fixtures', 'config.yml')
|
6
|
+
test_log_path = File.join(root, 'spec', 'fixtures', 'test.log')
|
7
|
+
|
8
|
+
Fog.mock!
|
9
|
+
test_logger = Logger.new(test_log_path)
|
10
|
+
config = YAML.load_file(config_file_path)
|
11
|
+
|
12
|
+
|
13
|
+
describe 'CloudDyndns::Updater' do
|
14
|
+
|
15
|
+
after do
|
16
|
+
FileUtils.rm_rf test_log_path
|
17
|
+
end
|
18
|
+
describe '.domain_name_to_provider_name' do
|
19
|
+
it 'should return the given name appended with a "."' do
|
20
|
+
result = CloudDyndns::Updater.domain_name_to_provider_name('ox')
|
21
|
+
|
22
|
+
expect(result).to_equal 'ox.'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '#initialize' do
|
27
|
+
it 'should set a dns interface' do
|
28
|
+
dns = CloudDyndns::Updater.new(config, test_logger)
|
29
|
+
|
30
|
+
expect(dns.dns).to_respond_to :zones
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should set a log interface' do
|
34
|
+
dns = CloudDyndns::Updater.new(config, test_logger)
|
35
|
+
|
36
|
+
expect(dns.log.class).to_equal ::Logger
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should set zone_configs' do
|
40
|
+
dns = CloudDyndns::Updater.new(config, test_logger)
|
41
|
+
|
42
|
+
expect(dns.zone_configs).to_not_be_empty
|
43
|
+
expect(dns.zone_configs[0][:domain]).to_equal 'looting.biz'
|
44
|
+
expect(dns.zone_configs[0][:targets]).to_include 'phl.looting.biz'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe '#find_zone_by_name' do
|
49
|
+
before do
|
50
|
+
@dns = CloudDyndns::Updater.new(config, test_logger)
|
51
|
+
end
|
52
|
+
|
53
|
+
describe 'when a zone is found' do
|
54
|
+
before do
|
55
|
+
@mock_zone = {}
|
56
|
+
@dns.expects(:find_zone_by_domain).
|
57
|
+
with('looting.biz').
|
58
|
+
returns(@mock_zone)
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'should return that zone' do
|
62
|
+
expect(@dns.find_zone_by_name('looting.biz')).to_equal(@mock_zone)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe 'when a zone is not found' do
|
67
|
+
before do
|
68
|
+
@dns.expects(:find_zone_by_domain).
|
69
|
+
with('looting.biz').
|
70
|
+
returns(nil)
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'should create a new zone for the given domain' do
|
74
|
+
zone = @dns.find_zone_by_name('looting.biz')
|
75
|
+
|
76
|
+
expect(zone.domain).to_equal 'looting.biz'
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'should return a zone' do
|
81
|
+
dns = CloudDyndns::Updater.new(config, test_logger)
|
82
|
+
|
83
|
+
result = dns.find_zone_by_name('honk')
|
84
|
+
|
85
|
+
expect(result).to_respond_to :records
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe '#find_zone_by_domain' do
|
90
|
+
before do
|
91
|
+
@dns = CloudDyndns::Updater.new(config, test_logger)
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'should return a domain if it matches the domain with a trailing "."' do
|
95
|
+
zone = @dns.zones.create(
|
96
|
+
:domain => 'looting.biz.'
|
97
|
+
)
|
98
|
+
found_zone = @dns.find_zone_by_domain('looting.biz')
|
99
|
+
|
100
|
+
expect(found_zone.domain).to_equal 'looting.biz.'
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'should return nil if not found' do
|
104
|
+
@dns.zones.clear()
|
105
|
+
found_zone = @dns.find_zone_by_domain('narf.io')
|
106
|
+
|
107
|
+
expect(found_zone).to_be_nil
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
describe '#create_zone_by_domain' do
|
112
|
+
before do
|
113
|
+
@dns = CloudDyndns::Updater.new(config, test_logger)
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'should create a zone for the passed domain' do
|
117
|
+
zone = @dns.create_zone_by_domain('looting.biz')
|
118
|
+
|
119
|
+
expect(zone.domain).to_equal 'looting.biz'
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'should set the :email as hostmaster@{{passed domain}}' do
|
123
|
+
@dns.stubs(:zones).returns(mock_zones = mock())
|
124
|
+
|
125
|
+
mock_zones.expects(:create).with(({
|
126
|
+
:domain => 'example.com',
|
127
|
+
:email => 'hostmaster@example.com'
|
128
|
+
})
|
129
|
+
)
|
130
|
+
|
131
|
+
@dns.create_zone_by_domain('example.com')
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
describe '#records_for_zone' do
|
136
|
+
before do
|
137
|
+
@dns = CloudDyndns::Updater.new(config, test_logger)
|
138
|
+
end
|
139
|
+
|
140
|
+
it 'should get the most up to date records for the zone' do
|
141
|
+
zone = mock()
|
142
|
+
records = mock()
|
143
|
+
zone.expects(:records).returns(records)
|
144
|
+
records.expects(:reload)
|
145
|
+
|
146
|
+
@dns.records_for_zone(zone)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
describe '#find_record_for_zone_by_name' do
|
151
|
+
before do
|
152
|
+
@dns = CloudDyndns::Updater.new(config, test_logger)
|
153
|
+
end
|
154
|
+
|
155
|
+
it 'should return the zone if the name matches the given name + "."' do
|
156
|
+
zone = @dns.zones.create(:domain => 'horseblood.biz')
|
157
|
+
# openstruct because it behaves like the returned object
|
158
|
+
record = OpenStruct.new(
|
159
|
+
:name => 'phl.horseblood.biz.',
|
160
|
+
:value => "8.8.8.8",
|
161
|
+
:type => 'A'
|
162
|
+
)
|
163
|
+
|
164
|
+
@dns.stubs(:records_for_zone).returns([record])
|
165
|
+
|
166
|
+
expect(@dns.find_record_for_zone_by_name(zone, 'phl.horseblood.biz')).
|
167
|
+
to_equal record
|
168
|
+
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
describe '#update_record_for_zone' do
|
173
|
+
|
174
|
+
before do
|
175
|
+
@dns = CloudDyndns::Updater.new(config, test_logger)
|
176
|
+
end
|
177
|
+
|
178
|
+
describe 'when current_ip is empty' do
|
179
|
+
it 'should raise error' do
|
180
|
+
@dns.stubs(:get_ip_address).returns('')
|
181
|
+
zone = mock()
|
182
|
+
target = 'phl.looting.biz'
|
183
|
+
|
184
|
+
expect(lambda {@dns.update_record_for_zone(zone, target) }).
|
185
|
+
to_raise CloudDyndns::Updater::NoIPAddressError
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
describe 'when current ip is not empty' do
|
190
|
+
before do
|
191
|
+
@dns.stubs(:get_ip_address).returns('8.8.4.4')
|
192
|
+
end
|
193
|
+
|
194
|
+
describe 'when record does not exist' do
|
195
|
+
before do
|
196
|
+
@dns.stubs(:find_record_for_zone_by_name).returns(nil)
|
197
|
+
end
|
198
|
+
|
199
|
+
it 'should call create_record_for_zone' do
|
200
|
+
zone = mock()
|
201
|
+
expected_attrs = {
|
202
|
+
:name => 'ff.phl.looting.biz',
|
203
|
+
:value => '8.8.4.4',
|
204
|
+
:ttl => nil
|
205
|
+
}
|
206
|
+
|
207
|
+
@dns.expects(:create_record_for_zone).with(zone, expected_attrs)
|
208
|
+
|
209
|
+
@dns.update_record_for_zone(zone, 'ff.phl.looting.biz')
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
describe 'when record exists' do
|
214
|
+
before do
|
215
|
+
@mock_record = mock()
|
216
|
+
@dns.stubs(:find_record_for_zone_by_name).returns(@mock_record)
|
217
|
+
end
|
218
|
+
|
219
|
+
describe 'and matches current ip address' do
|
220
|
+
before do
|
221
|
+
@mock_zone = mock()
|
222
|
+
@mock_record = OpenStruct.new(
|
223
|
+
:name => 'f.phl.narf.io',
|
224
|
+
:value => ['8.8.4.4']
|
225
|
+
)
|
226
|
+
@dns.stubs(:get_ip_address).returns('8.8.4.4')
|
227
|
+
@dns.stubs(:find_record_for_zone_by_name).returns(@mock_record)
|
228
|
+
end
|
229
|
+
|
230
|
+
it 'should not call :create_record_for_zone' do
|
231
|
+
@dns.expects(:create_record_for_zone).never
|
232
|
+
|
233
|
+
@dns.update_record_for_zone(@mock_zone, 'f.phl.narf.io')
|
234
|
+
end
|
235
|
+
|
236
|
+
it 'should return record' do
|
237
|
+
result = @dns.update_record_for_zone(@mock_zone, 'f.phl.narf.io')
|
238
|
+
|
239
|
+
expect(result).to_equal @mock_record
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
describe 'and does not match current ip address' do
|
244
|
+
before do
|
245
|
+
@mock_zone = mock()
|
246
|
+
@mock_record = OpenStruct.new(
|
247
|
+
:name => 'f.phl.narf.io',
|
248
|
+
:value => ['192.168.1.12']
|
249
|
+
)
|
250
|
+
@dns.stubs(:get_ip_address).returns('10.30.1.2')
|
251
|
+
@dns.stubs(:find_record_for_zone_by_name).returns(@mock_record)
|
252
|
+
end
|
253
|
+
|
254
|
+
it 'should destroy the record and create a new record' do
|
255
|
+
@mock_record.expects(:destroy)
|
256
|
+
|
257
|
+
@dns.expects(:create_record_for_zone)
|
258
|
+
@dns.update_record_for_zone(@mock_zone, 'f.phl.narf.io')
|
259
|
+
end
|
260
|
+
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
describe '#update!' do
|
267
|
+
describe 'when domains to update are not set' do
|
268
|
+
before do
|
269
|
+
empty_config = config.clone
|
270
|
+
empty_config[:zones].first[:targets] = nil
|
271
|
+
|
272
|
+
@dns = CloudDyndns::Updater.new(empty_config, test_logger)
|
273
|
+
end
|
274
|
+
|
275
|
+
it 'should raise error' do
|
276
|
+
operation = lambda {
|
277
|
+
@dns.update!
|
278
|
+
}
|
279
|
+
expect(operation).to_raise CloudDyndns::Updater::NoTargetsSpecified
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
describe 'when domains are set' do
|
284
|
+
before do
|
285
|
+
@dns = CloudDyndns::Updater.new(config, test_logger)
|
286
|
+
end
|
287
|
+
|
288
|
+
it 'should update the domains for each zone' do
|
289
|
+
mock_zone1 = mock()
|
290
|
+
mock_zone2 = mock()
|
291
|
+
|
292
|
+
@dns.expects(:find_zone_by_name).
|
293
|
+
with('looting.biz').returns(mock_zone1)
|
294
|
+
@dns.expects(:find_zone_by_name).
|
295
|
+
with('narf.io').returns(mock_zone2)
|
296
|
+
|
297
|
+
@dns.expects(:update_record_for_zone).
|
298
|
+
with(mock_zone1, 'phl.looting.biz', config[:zones][0])
|
299
|
+
@dns.expects(:update_record_for_zone).
|
300
|
+
with(mock_zone1, '*.phl.looting.biz', config[:zones][0])
|
301
|
+
|
302
|
+
@dns.expects(:update_record_for_zone).
|
303
|
+
with(mock_zone2, 'phl.narf.io', config[:zones][1])
|
304
|
+
@dns.expects(:update_record_for_zone).
|
305
|
+
with(mock_zone2, '*.phl.narf.io', config[:zones][1])
|
306
|
+
|
307
|
+
@dns.update!
|
308
|
+
end
|
309
|
+
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
end
|
metadata
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cloud-dyndns
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Nat Lownes
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-11-19 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: fog
|
16
|
+
requirement: &2164884840 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - =
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.18.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *2164884840
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: bundler
|
27
|
+
requirement: &2164884340 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '1.3'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *2164884340
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rake
|
38
|
+
requirement: &2164883960 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *2164883960
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: minitest-reporters
|
49
|
+
requirement: &2164883480 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *2164883480
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: minitest-matchers
|
60
|
+
requirement: &2164880620 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *2164880620
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: minitest
|
71
|
+
requirement: &2164880200 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: *2164880200
|
80
|
+
- !ruby/object:Gem::Dependency
|
81
|
+
name: mocha
|
82
|
+
requirement: &2164879700 !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ! '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
type: :development
|
89
|
+
prerelease: false
|
90
|
+
version_requirements: *2164879700
|
91
|
+
- !ruby/object:Gem::Dependency
|
92
|
+
name: minitest-spec-expect
|
93
|
+
requirement: &2164879000 !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ! '>='
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
type: :development
|
100
|
+
prerelease: false
|
101
|
+
version_requirements: *2164879000
|
102
|
+
description: ! '
|
103
|
+
|
104
|
+
update DNS entries on cloud providers (Route 53 et al) based on your current external
|
105
|
+
IP address. think like a dyndns updater. no-ip, et al.
|
106
|
+
|
107
|
+
'
|
108
|
+
email:
|
109
|
+
- nat.lownes@gmail.com
|
110
|
+
executables:
|
111
|
+
- cloud-dyndns
|
112
|
+
extensions: []
|
113
|
+
extra_rdoc_files: []
|
114
|
+
files:
|
115
|
+
- .gitignore
|
116
|
+
- Gemfile
|
117
|
+
- Gemfile.lock
|
118
|
+
- Guardfile
|
119
|
+
- LICENSE.txt
|
120
|
+
- README.md
|
121
|
+
- Rakefile
|
122
|
+
- bin/cloud-dyndns
|
123
|
+
- cloud-dyndns.gemspec
|
124
|
+
- cloud-dyndns.rb
|
125
|
+
- doc/crontab_example
|
126
|
+
- lib/config.rb
|
127
|
+
- lib/updater.rb
|
128
|
+
- lib/version.rb
|
129
|
+
- spec/config_spec.rb
|
130
|
+
- spec/fixtures/config.yml
|
131
|
+
- spec/test_helper.rb
|
132
|
+
- spec/updater_spec.rb
|
133
|
+
homepage: https://github.com/natlownes/cloud-dyndns
|
134
|
+
licenses:
|
135
|
+
- MIT
|
136
|
+
post_install_message:
|
137
|
+
rdoc_options: []
|
138
|
+
require_paths:
|
139
|
+
- lib
|
140
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
141
|
+
none: false
|
142
|
+
requirements:
|
143
|
+
- - ! '>='
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
147
|
+
none: false
|
148
|
+
requirements:
|
149
|
+
- - ! '>='
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
version: '0'
|
152
|
+
requirements: []
|
153
|
+
rubyforge_project:
|
154
|
+
rubygems_version: 1.8.10
|
155
|
+
signing_key:
|
156
|
+
specification_version: 3
|
157
|
+
summary: update DNS entries on cloud providers (Route 53 et al) based on your current
|
158
|
+
external IP address. think like a dyndns updater. no-ip, et al.
|
159
|
+
test_files:
|
160
|
+
- spec/config_spec.rb
|
161
|
+
- spec/fixtures/config.yml
|
162
|
+
- spec/test_helper.rb
|
163
|
+
- spec/updater_spec.rb
|