cloud-dyndns 0.0.1
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.
- 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
|