clouddns 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +114 -0
- data/bin/clouddns +10 -4
- data/lib/clouddns.rb +4 -3
- data/lib/clouddns/actions/migrate.rb +35 -57
- data/lib/clouddns/dsl.rb +49 -31
- data/lib/clouddns/version.rb +1 -1
- data/lib/clouddns/zone_migration.rb +61 -0
- data/test/dsl_test.rb +10 -0
- metadata +6 -4
data/README.md
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
clouddns
|
2
|
+
========
|
3
|
+
|
4
|
+
A DSL for managing your DNS on cloud services through [fog](http://fog.io/).
|
5
|
+
|
6
|
+
Clouddns allows a convenient way to manage DNS on Amazon Route 53, bluebox, DNSimple, DNS Made Easy, Linode DNS, Slicehost DNS or Zerigo DNS.
|
7
|
+
DNS records are described by configuration files in ruby, which are in a readable commentable format.
|
8
|
+
This also allows keeping your DNS configuration in version control.
|
9
|
+
Since DNS records are described in ruby, they can be defined programmatically, allowing DNS records to be made from database entries or other sources like a [chef server](http://www.opscode.com/chef/).
|
10
|
+
|
11
|
+
Installation
|
12
|
+
============
|
13
|
+
|
14
|
+
gem install clouddns
|
15
|
+
|
16
|
+
Usage
|
17
|
+
=====
|
18
|
+
|
19
|
+
To change your DNS to that in the config file run
|
20
|
+
|
21
|
+
clouddns migrate dns.rb
|
22
|
+
|
23
|
+
Where dns.rb is a file in the following format
|
24
|
+
|
25
|
+
Configuration file
|
26
|
+
==================
|
27
|
+
|
28
|
+
The configuration files used by clouddns are written in ruby and specify zones at the top level, and records within.
|
29
|
+
|
30
|
+
*Zones* group all records within a domain (usually, second-level or third-level).
|
31
|
+
Within these zones, records are declared in the format `TYPE 'FULL.DOMAIN.NAME' 'VALUE'`
|
32
|
+
|
33
|
+
Example configuration
|
34
|
+
---------------------
|
35
|
+
|
36
|
+
provider 'AWS'
|
37
|
+
|
38
|
+
defaults :ttl => 600
|
39
|
+
|
40
|
+
zone 'example.com' do
|
41
|
+
A 'example.com', '1.2.3.4'
|
42
|
+
CNAME 'www.example.com', 'example.com.'
|
43
|
+
|
44
|
+
A 'mail.example.com', '4.3.2.1'
|
45
|
+
MX 'www.example.com', '10 mail.example.com.', :ttl => 300
|
46
|
+
end
|
47
|
+
|
48
|
+
zone 'example.net' do
|
49
|
+
A 'www.example.net', '1.2.3.4'
|
50
|
+
end
|
51
|
+
|
52
|
+
Zone
|
53
|
+
----
|
54
|
+
|
55
|
+
Defines a zone. Takes one argument, the fully qualified domain name, and a block, which declares the records associated with the zone.
|
56
|
+
Domain names can end with a dot, if missing, is is implied.
|
57
|
+
|
58
|
+
Records
|
59
|
+
-------
|
60
|
+
|
61
|
+
Records must be defined within a zone.
|
62
|
+
They are defined by calling the helper method for their type (A, CNAME, etc) with their FQDN, value, and any options.
|
63
|
+
If the domain name is missing the trailing dot, it is implied.
|
64
|
+
|
65
|
+
Value can be an array to specify multiple entries for that record (like in the case of Round-robin DNS, or multiple MX records).
|
66
|
+
|
67
|
+
Options is currently only :ttl, which is specified in seconds. :ttl defaults to 3600, which can be changed using the defaults directive.
|
68
|
+
|
69
|
+
Provider (optional)
|
70
|
+
-------------------
|
71
|
+
|
72
|
+
Credentials are required to access whichever service is desired through dns.
|
73
|
+
This can be specified in full in the config file
|
74
|
+
|
75
|
+
provider 'AWS',
|
76
|
+
:aws_secret_access_key => YOUR_SECRET_ACCESS_KEY,
|
77
|
+
:aws_access_key_id => YOUR_SECRET_ACCESS_KEY_ID
|
78
|
+
|
79
|
+
Alternatively, to avoid specifying access keys or access key in the file (A good idea if it's to enter version control),
|
80
|
+
fog will read credentials from `~/.fog` or the file specified in `ENV["FOG_RC"]`.
|
81
|
+
One can then specify the provider by itself in the config file (like `provider 'AWS'`) or using the `-p PROVIDER` option of the clouddns executable.
|
82
|
+
|
83
|
+
Defaults
|
84
|
+
--------
|
85
|
+
|
86
|
+
Defaults specified options which will be used by any zones or records defined in the file.
|
87
|
+
Currently, this can only be used to specify :ttl.
|
88
|
+
|
89
|
+
|
90
|
+
Copyright
|
91
|
+
=========
|
92
|
+
|
93
|
+
(The MIT License)
|
94
|
+
|
95
|
+
Copyright (c) 2011 [John Hawthorn](http://www.johnhawthorn.com/)
|
96
|
+
|
97
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
98
|
+
a copy of this software and associated documentation files (the
|
99
|
+
"Software"), to deal in the Software without restriction, including
|
100
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
101
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
102
|
+
permit persons to whom the Software is furnished to do so, subject to
|
103
|
+
the following conditions:
|
104
|
+
|
105
|
+
The above copyright notice and this permission notice shall be
|
106
|
+
included in all copies or substantial portions of the Software.
|
107
|
+
|
108
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
109
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
110
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
111
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
112
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
113
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
114
|
+
|
data/bin/clouddns
CHANGED
@@ -37,11 +37,17 @@ files = ARGV
|
|
37
37
|
raise "No action specified" unless action
|
38
38
|
raise "No files specified" if files.empty?
|
39
39
|
|
40
|
+
action = Clouddns::Actions.by_name(action)
|
41
|
+
|
40
42
|
zones = files.map do |file|
|
41
|
-
Clouddns::DSL.parse_file(file)
|
42
|
-
|
43
|
+
dsl = Clouddns::DSL.parse_file(file)
|
44
|
+
|
45
|
+
dsloptions = {:fog => dsl.fog_options}.merge(options) do |key, oldval, newval|
|
46
|
+
oldval.merge(newval)
|
47
|
+
end
|
43
48
|
|
44
|
-
zones.each do |zone|
|
45
|
-
|
49
|
+
dsl.zones.each do |zone|
|
50
|
+
action.run(zone, dsloptions)
|
51
|
+
end
|
46
52
|
end
|
47
53
|
|
data/lib/clouddns.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
require "fog"
|
2
2
|
|
3
|
-
require "clouddns/
|
3
|
+
require "clouddns/actions"
|
4
4
|
require "clouddns/dsl"
|
5
|
-
require "clouddns/zone"
|
6
5
|
require "clouddns/record"
|
7
|
-
require "clouddns/
|
6
|
+
require "clouddns/version"
|
7
|
+
require "clouddns/zone"
|
8
|
+
require "clouddns/zone_migration"
|
8
9
|
|
9
10
|
module Clouddns
|
10
11
|
end
|
@@ -7,83 +7,61 @@ module Clouddns
|
|
7
7
|
|
8
8
|
def run
|
9
9
|
@fog = Fog::DNS.new(@options[:fog])
|
10
|
-
|
11
10
|
@fog_zone = @fog.zones.find { |z| z.domain == @zone.name }
|
12
11
|
|
13
12
|
puts
|
14
13
|
puts "Migrating '#{@zone.name}'"
|
15
14
|
|
16
|
-
unless @fog_zone
|
17
|
-
puts
|
18
|
-
puts "Zone '#{@zone.name}' does not exist. Creating..."
|
19
|
-
require_confirmation!
|
20
|
-
@fog_zone = @fog.zones.create(:domain => @zone.name)
|
21
|
-
puts "Zone '#{@zone.name}' created."
|
22
|
-
end
|
15
|
+
create_zone! unless @fog_zone
|
23
16
|
|
24
|
-
# required to pick up nameservers
|
17
|
+
# required to pick up nameservers and records
|
25
18
|
@fog_zone = @fog_zone.reload
|
26
19
|
|
27
|
-
|
28
|
-
puts "Nameservers:"
|
29
|
-
@fog_zone.nameservers.each do |nameserver|
|
30
|
-
puts " #{nameserver}"
|
31
|
-
end
|
32
|
-
|
33
|
-
puts
|
34
|
-
puts "Changes:"
|
35
|
-
@records_to_remove = []
|
36
|
-
@records_to_add = []
|
37
|
-
|
38
|
-
@namelength = (@fog_zone.records.map(&:name) + @zone.records.map(&:name)).map(&:length).max
|
39
|
-
|
40
|
-
@actions = []
|
41
|
-
|
42
|
-
fog_records = Hash[@fog_zone.records.map {|r| [[r.name.gsub('\052', '*'), r.type], r] } ]
|
43
|
-
@zone.records.each do |record|
|
44
|
-
if (fog_record = fog_records.delete([record.name, record.type]))
|
45
|
-
if equal?(record, fog_record)
|
46
|
-
@actions << [' ', record]
|
47
|
-
else
|
48
|
-
@actions << ['-', fog_record]
|
49
|
-
@actions << ['+', record]
|
50
|
-
end
|
51
|
-
else
|
52
|
-
@actions << ['+', record]
|
53
|
-
end
|
54
|
-
end
|
20
|
+
print_nameservers
|
55
21
|
|
56
|
-
|
57
|
-
@actions << ['-', record]
|
58
|
-
end
|
22
|
+
@migration = ZoneMigration.new(@zone, @fog_zone)
|
59
23
|
|
60
|
-
|
61
|
-
print_record record, @namelength, action
|
62
|
-
end
|
24
|
+
print_changes
|
63
25
|
|
64
26
|
# no changes required
|
65
|
-
return if @
|
27
|
+
return if @migration.completed?
|
66
28
|
|
67
29
|
require_confirmation!
|
68
30
|
|
69
|
-
@
|
70
|
-
|
71
|
-
@fog_zone.records.create(:type => record.type, :name => record.name, :ip => record.value, :ttl => record.ttl)
|
72
|
-
elsif action == '-'
|
73
|
-
record.destroy
|
74
|
-
end
|
75
|
-
end
|
31
|
+
@migration.perform!
|
32
|
+
|
76
33
|
puts "Done"
|
77
34
|
end
|
78
35
|
|
79
36
|
private
|
80
37
|
|
81
|
-
def
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
38
|
+
def create_zone!
|
39
|
+
puts
|
40
|
+
puts "Zone '#{@zone.name}' does not exist. Creating..."
|
41
|
+
require_confirmation!
|
42
|
+
@fog_zone = @fog.zones.create(:domain => @zone.name)
|
43
|
+
puts "Zone '#{@zone.name}' created."
|
44
|
+
end
|
45
|
+
|
46
|
+
def print_nameservers
|
47
|
+
puts
|
48
|
+
puts "Nameservers:"
|
49
|
+
@fog_zone.nameservers.each do |nameserver|
|
50
|
+
puts " #{nameserver}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def print_changes
|
55
|
+
namelength = @migration.changes.map do |(action, record)|
|
56
|
+
record.name.length
|
57
|
+
end.max
|
58
|
+
|
59
|
+
puts
|
60
|
+
puts "Changes:"
|
61
|
+
|
62
|
+
@migration.changes.each do |(action, record)|
|
63
|
+
print_record record, namelength, action
|
64
|
+
end
|
87
65
|
end
|
88
66
|
|
89
67
|
def require_confirmation!
|
data/lib/clouddns/dsl.rb
CHANGED
@@ -1,20 +1,16 @@
|
|
1
1
|
module Clouddns
|
2
2
|
class DSL
|
3
3
|
attr_reader :zones
|
4
|
+
attr_reader :fog_options
|
4
5
|
|
5
6
|
# This need not be too strict. Only exists to help catch typos.
|
6
7
|
DNS_REGEX = /\A.*\..*\.\z/
|
7
8
|
|
8
|
-
def scope options={}
|
9
|
-
oldscope = @scope
|
10
|
-
@scope = @scope.merge(options)
|
11
|
-
yield
|
12
|
-
@scope = oldscope
|
13
|
-
end
|
14
|
-
|
15
9
|
def initialize
|
16
10
|
@zones = []
|
17
|
-
@
|
11
|
+
@zone = nil
|
12
|
+
@defaults = {}
|
13
|
+
@fog_options = {}
|
18
14
|
end
|
19
15
|
|
20
16
|
def self.parse_string string
|
@@ -26,37 +22,59 @@ module Clouddns
|
|
26
22
|
parse_string open(filename).read
|
27
23
|
end
|
28
24
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
add_record 'MX', *args
|
37
|
-
end
|
38
|
-
def NS *args
|
39
|
-
add_record 'NS', *args
|
40
|
-
end
|
41
|
-
def TXT name, value, options={}
|
42
|
-
add_record 'TXT', name, "\"#{value}\"", options
|
25
|
+
protected
|
26
|
+
|
27
|
+
record_types = %w{A AAAA CNAME MX NS PTR SOA SPF SRV TXT}
|
28
|
+
record_types.each do |type|
|
29
|
+
define_method type do |*args|
|
30
|
+
add_record type, *args
|
31
|
+
end
|
43
32
|
end
|
44
33
|
|
45
34
|
def add_record type, name, value, options={}
|
46
|
-
|
35
|
+
name = domainname(name)
|
36
|
+
|
37
|
+
value = "\"#{value}\"" if type == 'TXT'
|
47
38
|
|
48
|
-
raise "records must be added inside a zone" unless zone
|
49
|
-
raise "record's dns name must end with the current zone" unless name.end_with? zone.name
|
39
|
+
raise "records must be added inside a zone" unless @zone
|
40
|
+
raise "record's dns name must end with the current zone" unless name.end_with? @zone.name
|
50
41
|
|
51
|
-
@
|
42
|
+
@zone.records << Record.new(type, name, value, options)
|
52
43
|
end
|
53
44
|
|
54
|
-
def zone name
|
55
|
-
raise "
|
45
|
+
def zone name
|
46
|
+
raise "zones cannot be nested" if @zone
|
56
47
|
|
57
|
-
zone = Zone.new(name)
|
58
|
-
@zones << zone
|
59
|
-
|
48
|
+
@zone = Zone.new(domainname(name))
|
49
|
+
@zones << @zone
|
50
|
+
yield
|
51
|
+
@zone = nil
|
52
|
+
end
|
53
|
+
|
54
|
+
def defaults options={}
|
55
|
+
if block_given?
|
56
|
+
old = @defaults
|
57
|
+
@defaults = @defaults.merge(options)
|
58
|
+
yield
|
59
|
+
@defaults = old
|
60
|
+
else
|
61
|
+
raise "defaults must either take a block or be before any zone declarations" unless @zones.empty?
|
62
|
+
@defaults = @defaults.merge(options)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def provider name, options={}
|
67
|
+
@fog_options = options.merge({:provider => name})
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
def domainname name
|
72
|
+
if name.end_with? '.'
|
73
|
+
name
|
74
|
+
else
|
75
|
+
"#{name}."
|
76
|
+
end
|
60
77
|
end
|
61
78
|
end
|
62
79
|
end
|
80
|
+
|
data/lib/clouddns/version.rb
CHANGED
@@ -0,0 +1,61 @@
|
|
1
|
+
module Clouddns
|
2
|
+
class ZoneMigration
|
3
|
+
def initialize zone, fog_zone
|
4
|
+
@zone = zone
|
5
|
+
@fog_zone = fog_zone
|
6
|
+
@changes = nil
|
7
|
+
end
|
8
|
+
def changes
|
9
|
+
return @changes if @changes
|
10
|
+
@changes = []
|
11
|
+
fog_records = Hash[@fog_zone.records.map {|r| [[fog_record_name(r), r.type], r] } ]
|
12
|
+
@zone.records.each do |record|
|
13
|
+
if (fog_record = fog_records.delete([record.name, record.type]))
|
14
|
+
if records_equal?(record, fog_record)
|
15
|
+
@changes << [' ', record]
|
16
|
+
else
|
17
|
+
@changes << ['-', fog_record]
|
18
|
+
@changes << ['+', record]
|
19
|
+
end
|
20
|
+
else
|
21
|
+
@changes << ['+', record]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
fog_records.each do |name, record|
|
26
|
+
@changes << ['-', record]
|
27
|
+
end
|
28
|
+
@changes
|
29
|
+
end
|
30
|
+
|
31
|
+
def perform!
|
32
|
+
changes.each do |(action, record)|
|
33
|
+
if action == '+'
|
34
|
+
@fog_zone.records.create(:type => record.type, :name => record.name, :ip => record.value, :ttl => record.ttl)
|
35
|
+
elsif action == '-'
|
36
|
+
record.destroy
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def completed?
|
42
|
+
changes.all? do |(action, record)|
|
43
|
+
action == ' '
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
protected
|
48
|
+
def fog_record_name record
|
49
|
+
record.name.gsub('\052', '*')
|
50
|
+
end
|
51
|
+
|
52
|
+
def records_equal? record, fog_record
|
53
|
+
# AWS replaces * with \052
|
54
|
+
record.name == fog_record_name(fog_record) &&
|
55
|
+
record.type == fog_record.type &&
|
56
|
+
record.value == fog_record.ip &&
|
57
|
+
record.ttl.to_i == fog_record.ttl.to_i
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
data/test/dsl_test.rb
CHANGED
@@ -19,5 +19,15 @@ class DslTest < Test::Unit::TestCase
|
|
19
19
|
assert_equal 'www.example.com.', record.name
|
20
20
|
assert_equal ['1.2.3.4'], record.value
|
21
21
|
end
|
22
|
+
def test_trailing_dot_is_implied
|
23
|
+
dsl = Clouddns::DSL.parse_string <<-EOL
|
24
|
+
zone "example.com" do
|
25
|
+
A 'www.example.com', '1.2.3.4'
|
26
|
+
end
|
27
|
+
EOL
|
28
|
+
|
29
|
+
assert_equal 'example.com.', dsl.zones.first.name
|
30
|
+
assert_equal 'www.example.com.', dsl.zones.first.records.first.name
|
31
|
+
end
|
22
32
|
end
|
23
33
|
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: clouddns
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 27
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
9
|
+
- 2
|
10
|
+
version: 0.0.2
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- John Hawthorn
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-07-
|
18
|
+
date: 2011-07-14 00:00:00 Z
|
19
19
|
dependencies: []
|
20
20
|
|
21
21
|
description: A DSL for managing DNS through cloud services
|
@@ -30,6 +30,7 @@ extra_rdoc_files: []
|
|
30
30
|
files:
|
31
31
|
- .gitignore
|
32
32
|
- Gemfile
|
33
|
+
- README.md
|
33
34
|
- Rakefile
|
34
35
|
- bin/clouddns
|
35
36
|
- clouddns.gemspec
|
@@ -43,6 +44,7 @@ files:
|
|
43
44
|
- lib/clouddns/record.rb
|
44
45
|
- lib/clouddns/version.rb
|
45
46
|
- lib/clouddns/zone.rb
|
47
|
+
- lib/clouddns/zone_migration.rb
|
46
48
|
- test/dsl_test.rb
|
47
49
|
- test/test_helper.rb
|
48
50
|
homepage: https://github.com/jhawthorn/clouddns
|