zonesync 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rspec +1 -1
- data/Gemfile +1 -0
- data/README.md +3 -0
- data/lib/zonesync/cloudflare.rb +3 -0
- data/lib/zonesync/provider.rb +4 -2
- data/lib/zonesync/record.rb +5 -2
- data/lib/zonesync/version.rb +1 -1
- data/lib/zonesync/zonefile.rb +318 -0
- data/lib/zonesync/zonefile.treetop +586 -0
- data/zonesync.gemspec +1 -1
- metadata +13 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9f636466fb4175eab983daffca93a4f3e09447bd49a4b50944078034c9067b46
|
4
|
+
data.tar.gz: 462dcac524149c882e25e8f1962c9b29cc376177448b5a8320241cb135a83b01
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '0628bc131c08e8432e82c7b1f9a94587224871434df282ad52adc3cc1fe04b755c2b73ba849ee707a4c9c3ab6dfb69cd99b1289e6cfde41df9c72b1ccc874485'
|
7
|
+
data.tar.gz: 4f55bace0450c64863dccbe394d160b00197db1294cc227e2fa119310c89415c5b6bf4ee52a9bd2d1e68a9aa362376e3079552947cc9703df3ee4e562d8e5fff
|
data/.rspec
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
--color
|
2
|
-
--require
|
2
|
+
--require spec_helper
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -48,8 +48,11 @@ wwwtest CNAME www
|
|
48
48
|
mail A 192.0.2.3
|
49
49
|
mail2 A 192.0.2.4
|
50
50
|
mail3 A 192.0.2.5
|
51
|
+
ignore A 192.0.2.6 ; zonesync: ignore
|
51
52
|
```
|
52
53
|
|
54
|
+
Note that records with a comment containing "zonesync: ignore" will not be touched during the sync. I'm considering inverting this from a blacklist to a whitelist in a future version, to avoid stomping on collaborators' records.
|
55
|
+
|
53
56
|
### DNS Host
|
54
57
|
|
55
58
|
We need to tell `zonesync` about our DNS host by building a small YAML file. The structure of this file will depend on your DNS host, so here are some examples:
|
data/lib/zonesync/cloudflare.rb
CHANGED
@@ -19,6 +19,7 @@ module Zonesync
|
|
19
19
|
type: new_record[:type],
|
20
20
|
ttl: new_record[:ttl],
|
21
21
|
content: new_record[:rdata],
|
22
|
+
comment: new_record[:comment],
|
22
23
|
})
|
23
24
|
end
|
24
25
|
|
@@ -28,6 +29,7 @@ module Zonesync
|
|
28
29
|
type: record[:type],
|
29
30
|
ttl: record[:ttl],
|
30
31
|
content: record[:rdata],
|
32
|
+
comment: record[:comment],
|
31
33
|
})
|
32
34
|
end
|
33
35
|
|
@@ -55,6 +57,7 @@ module Zonesync
|
|
55
57
|
attrs["type"],
|
56
58
|
attrs["ttl"].to_i,
|
57
59
|
rdata,
|
60
|
+
attrs["comment"],
|
58
61
|
).to_h
|
59
62
|
end
|
60
63
|
|
data/lib/zonesync/provider.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
require "dns/zonefile"
|
2
1
|
require "zonesync/record"
|
2
|
+
require "zonesync/zonefile"
|
3
3
|
|
4
4
|
module Zonesync
|
5
5
|
class Provider < Struct.new(:credentials)
|
@@ -13,6 +13,8 @@ module Zonesync
|
|
13
13
|
Record.from_dns_zonefile_record(record)
|
14
14
|
end.select do |record|
|
15
15
|
%w[A AAAA CNAME MX TXT SPF NAPTR PTR].include?(record.type)
|
16
|
+
end.reject do |record|
|
17
|
+
record.comment.to_s.downcase.include? "zonesync: ignore"
|
16
18
|
end.sort
|
17
19
|
end
|
18
20
|
|
@@ -21,7 +23,7 @@ module Zonesync
|
|
21
23
|
if body !~ /\sSOA\s/ # insert dummy SOA to trick parser if needed
|
22
24
|
body.sub!(/\n([^$])/, "\n@ 1 SOA example.com example.com ( 2000010101 1 1 1 1 )\n\\1")
|
23
25
|
end
|
24
|
-
|
26
|
+
Zonefile.load(body)
|
25
27
|
end
|
26
28
|
|
27
29
|
def read record
|
data/lib/zonesync/record.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
module Zonesync
|
2
|
-
class Record < Struct.new(:name, :type, :ttl, :rdata)
|
2
|
+
class Record < Struct.new(:name, :type, :ttl, :rdata, :comment)
|
3
3
|
def self.from_dns_zonefile_record record
|
4
4
|
type = record.class.name.split("::").last
|
5
5
|
rdata = case type
|
@@ -23,6 +23,7 @@ module Zonesync
|
|
23
23
|
type,
|
24
24
|
record.ttl,
|
25
25
|
rdata,
|
26
|
+
record.comment,
|
26
27
|
)
|
27
28
|
end
|
28
29
|
|
@@ -35,7 +36,9 @@ module Zonesync
|
|
35
36
|
end
|
36
37
|
|
37
38
|
def to_s
|
38
|
-
|
39
|
+
string = [name, type, ttl, rdata].join(" ")
|
40
|
+
string << " ; #{comment}" if comment
|
41
|
+
string
|
39
42
|
end
|
40
43
|
end
|
41
44
|
end
|
data/lib/zonesync/version.rb
CHANGED
@@ -0,0 +1,318 @@
|
|
1
|
+
require "treetop"
|
2
|
+
Treetop.load File.join(__dir__, "zonefile")
|
3
|
+
|
4
|
+
module Zonesync
|
5
|
+
module Zonefile
|
6
|
+
class << self
|
7
|
+
def parse(zone_string)
|
8
|
+
parser = ZonefileParser.new
|
9
|
+
result = parser.parse(zone_string)
|
10
|
+
return result if result
|
11
|
+
raise ParsingError, parser.failure_reason
|
12
|
+
end
|
13
|
+
|
14
|
+
def load(zone_string, alternate_origin = nil)
|
15
|
+
Zone.new(parse(zone_string).entries, alternate_origin)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class ParsingError < RuntimeError; end
|
20
|
+
class UnknownRecordType < RuntimeError; end
|
21
|
+
class Zone
|
22
|
+
attr_reader :origin
|
23
|
+
attr_reader :records
|
24
|
+
|
25
|
+
def initialize(entries, alternate_origin = nil)
|
26
|
+
alternate_origin ||= "."
|
27
|
+
@records = []
|
28
|
+
@vars = {"origin" => alternate_origin, :last_host => "."}
|
29
|
+
entries.each do |e|
|
30
|
+
case e.parse_type
|
31
|
+
when :variable
|
32
|
+
key = e.name.text_value.downcase
|
33
|
+
@vars[key] = case key
|
34
|
+
when "ttl"
|
35
|
+
e.value.text_value.to_i
|
36
|
+
else
|
37
|
+
e.value.text_value
|
38
|
+
end
|
39
|
+
when :soa
|
40
|
+
@records << SOA.new(@vars, e)
|
41
|
+
when :record
|
42
|
+
case e.record_type
|
43
|
+
when "A" then @records << A.new(@vars, e)
|
44
|
+
when "AAAA" then @records << AAAA.new(@vars, e)
|
45
|
+
when "CAA" then @records << CAA.new(@vars, e)
|
46
|
+
when "CNAME" then @records << CNAME.new(@vars, e)
|
47
|
+
when "MX" then @records << MX.new(@vars, e)
|
48
|
+
when "NAPTR" then @records << NAPTR.new(@vars, e)
|
49
|
+
when "NS" then @records << NS.new(@vars, e)
|
50
|
+
when "PTR" then @records << PTR.new(@vars, e)
|
51
|
+
when "SRV" then @records << SRV.new(@vars, e)
|
52
|
+
when "SPF" then @records << SPF.new(@vars, e)
|
53
|
+
when "SSHFP" then @records << SSHFP.new(@vars, e)
|
54
|
+
when "TXT" then @records << TXT.new(@vars, e)
|
55
|
+
when "SOA" then
|
56
|
+
# No-op
|
57
|
+
else
|
58
|
+
raise UnknownRecordType, "Unknown record type: #{e.record_type}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def soa
|
65
|
+
records_of(SOA).first
|
66
|
+
end
|
67
|
+
|
68
|
+
def records_of(kl)
|
69
|
+
@records.select { |r| r.instance_of? kl }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class Record
|
74
|
+
# assign, with handling for global TTL
|
75
|
+
def self.writer_for_ttl(*attribs)
|
76
|
+
attribs.each do |attrib|
|
77
|
+
define_method "#{attrib}=" do |val|
|
78
|
+
instance_variable_set("@#{attrib}", val || @vars["ttl"])
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
attr_reader :ttl
|
84
|
+
attr_writer :klass
|
85
|
+
writer_for_ttl :ttl
|
86
|
+
|
87
|
+
def klass
|
88
|
+
@klass = nil if @klass == ""
|
89
|
+
@klass ||= "IN"
|
90
|
+
end
|
91
|
+
|
92
|
+
attr_accessor :comment
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def qualify_host(host)
|
97
|
+
origin = vars["origin"]
|
98
|
+
host = vars[:last_host] if /^\s*$/.match?(host)
|
99
|
+
host = host.gsub(/@/, origin)
|
100
|
+
if /\.$/.match?(host)
|
101
|
+
host
|
102
|
+
elsif /^\./.match?(origin)
|
103
|
+
host + origin
|
104
|
+
else
|
105
|
+
host + "." + origin
|
106
|
+
end
|
107
|
+
end
|
108
|
+
attr_accessor :vars
|
109
|
+
end
|
110
|
+
|
111
|
+
class SOA < Record
|
112
|
+
attr_accessor :origin, :nameserver, :responsible_party, :serial, :refresh_time, :retry_time, :expiry_time, :nxttl
|
113
|
+
|
114
|
+
def initialize(vars, zonefile_soa = nil)
|
115
|
+
@vars = vars
|
116
|
+
if zonefile_soa
|
117
|
+
self.origin = qualify_host(zonefile_soa.origin.to_s)
|
118
|
+
@vars[:last_host] = origin
|
119
|
+
self.ttl = zonefile_soa.ttl.to_i
|
120
|
+
self.klass = zonefile_soa.klass.to_s
|
121
|
+
self.nameserver = qualify_host(zonefile_soa.ns.to_s)
|
122
|
+
self.responsible_party = qualify_host(zonefile_soa.rp.to_s)
|
123
|
+
self.serial = zonefile_soa.serial.to_i
|
124
|
+
self.refresh_time = zonefile_soa.refresh.to_i
|
125
|
+
self.retry_time = zonefile_soa.reretry.to_i
|
126
|
+
self.expiry_time = zonefile_soa.expiry.to_i
|
127
|
+
self.nxttl = zonefile_soa.nxttl.to_i
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
class A < Record
|
133
|
+
attr_accessor :host, :address
|
134
|
+
|
135
|
+
def initialize(vars, zonefile_record)
|
136
|
+
@vars = vars
|
137
|
+
if zonefile_record
|
138
|
+
self.host = qualify_host(zonefile_record.host.to_s)
|
139
|
+
@vars[:last_host] = host
|
140
|
+
self.ttl = zonefile_record.ttl.to_i
|
141
|
+
self.klass = zonefile_record.klass.to_s
|
142
|
+
self.address = zonefile_record.ip_address.to_s
|
143
|
+
self.comment = zonefile_record.comment&.to_s
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
class AAAA < A
|
149
|
+
end
|
150
|
+
|
151
|
+
class CAA < Record
|
152
|
+
attr_accessor :host, :flags, :tag, :value
|
153
|
+
|
154
|
+
def initialize(vars, zonefile_record)
|
155
|
+
@vars = vars
|
156
|
+
if zonefile_record
|
157
|
+
self.host = qualify_host(zonefile_record.host.to_s)
|
158
|
+
@vars[:last_host] = host
|
159
|
+
self.ttl = zonefile_record.ttl.to_i
|
160
|
+
self.klass = zonefile_record.klass.to_s
|
161
|
+
self.flags = zonefile_record.flags.to_i
|
162
|
+
self.tag = zonefile_record.tag.to_s
|
163
|
+
self.value = zonefile_record.value.to_s
|
164
|
+
self.comment = zonefile_record.comment&.to_s
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
class CNAME < Record
|
170
|
+
attr_accessor :host, :domainname
|
171
|
+
|
172
|
+
def initialize(vars, zonefile_record)
|
173
|
+
@vars = vars
|
174
|
+
if zonefile_record
|
175
|
+
self.host = qualify_host(zonefile_record.host.to_s)
|
176
|
+
@vars[:last_host] = host
|
177
|
+
self.ttl = zonefile_record.ttl.to_i
|
178
|
+
self.klass = zonefile_record.klass.to_s
|
179
|
+
self.domainname = qualify_host(zonefile_record.target.to_s)
|
180
|
+
self.comment = zonefile_record.comment&.to_s
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
alias target domainname
|
185
|
+
alias alias host
|
186
|
+
end
|
187
|
+
|
188
|
+
class MX < Record
|
189
|
+
attr_accessor :host, :priority, :domainname
|
190
|
+
|
191
|
+
def initialize(vars, zonefile_record)
|
192
|
+
@vars = vars
|
193
|
+
if zonefile_record
|
194
|
+
self.host = qualify_host(zonefile_record.host.to_s)
|
195
|
+
@vars[:last_host] = host
|
196
|
+
self.ttl = zonefile_record.ttl.to_i
|
197
|
+
self.klass = zonefile_record.klass.to_s
|
198
|
+
self.priority = zonefile_record.priority.to_i
|
199
|
+
self.domainname = qualify_host(zonefile_record.exchanger.to_s)
|
200
|
+
self.comment = zonefile_record.comment&.to_s
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
alias exchange domainname
|
205
|
+
alias exchanger domainname
|
206
|
+
end
|
207
|
+
|
208
|
+
class NAPTR < Record
|
209
|
+
attr_accessor :host, :data
|
210
|
+
|
211
|
+
def initialize(vars, zonefile_record)
|
212
|
+
@vars = vars
|
213
|
+
if zonefile_record
|
214
|
+
self.host = qualify_host(zonefile_record.host.to_s)
|
215
|
+
@vars[:last_host] = host
|
216
|
+
self.ttl = zonefile_record.ttl.to_i
|
217
|
+
self.klass = zonefile_record.klass.to_s
|
218
|
+
self.data = zonefile_record.data.to_s
|
219
|
+
self.comment = zonefile_record.comment&.to_s
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
class NS < Record
|
225
|
+
attr_accessor :host, :domainname
|
226
|
+
|
227
|
+
def initialize(vars, zonefile_record)
|
228
|
+
@vars = vars
|
229
|
+
if zonefile_record
|
230
|
+
self.host = qualify_host(zonefile_record.host.to_s)
|
231
|
+
@vars[:last_host] = host
|
232
|
+
self.ttl = zonefile_record.ttl.to_i
|
233
|
+
self.klass = zonefile_record.klass.to_s
|
234
|
+
self.domainname = qualify_host(zonefile_record.nameserver.to_s)
|
235
|
+
self.comment = zonefile_record.comment&.to_s
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
alias nameserver domainname
|
240
|
+
end
|
241
|
+
|
242
|
+
class PTR < Record
|
243
|
+
attr_accessor :host, :domainname
|
244
|
+
|
245
|
+
def initialize(vars, zonefile_record)
|
246
|
+
@vars = vars
|
247
|
+
if zonefile_record
|
248
|
+
self.host = qualify_host(zonefile_record.host.to_s)
|
249
|
+
@vars[:last_host] = host
|
250
|
+
self.ttl = zonefile_record.ttl.to_i
|
251
|
+
self.klass = zonefile_record.klass.to_s
|
252
|
+
self.domainname = qualify_host(zonefile_record.target.to_s)
|
253
|
+
self.comment = zonefile_record.comment&.to_s
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
alias target domainname
|
258
|
+
end
|
259
|
+
|
260
|
+
class SRV < Record
|
261
|
+
attr_accessor :host, :priority, :weight, :port, :domainname
|
262
|
+
|
263
|
+
def initialize(vars, zonefile_record)
|
264
|
+
@vars = vars
|
265
|
+
if zonefile_record
|
266
|
+
self.host = qualify_host(zonefile_record.host.to_s)
|
267
|
+
@vars[:last_host] = host
|
268
|
+
self.ttl = zonefile_record.ttl.to_i
|
269
|
+
self.klass = zonefile_record.klass.to_s
|
270
|
+
self.priority = zonefile_record.priority.to_i
|
271
|
+
self.weight = zonefile_record.weight.to_i
|
272
|
+
self.port = zonefile_record.port.to_i
|
273
|
+
self.domainname = qualify_host(zonefile_record.target.to_s)
|
274
|
+
self.comment = zonefile_record.comment&.to_s
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
alias target domainname
|
279
|
+
end
|
280
|
+
|
281
|
+
class SSHFP < Record
|
282
|
+
attr_accessor :host, :alg, :fptype, :fp
|
283
|
+
|
284
|
+
def initialize(vars, zonefile_record)
|
285
|
+
@vars = vars
|
286
|
+
if zonefile_record
|
287
|
+
self.host = qualify_host(zonefile_record.host.to_s)
|
288
|
+
@vars[:last_host] = host
|
289
|
+
self.ttl = zonefile_record.ttl.to_i
|
290
|
+
self.klass = zonefile_record.klass.to_s
|
291
|
+
self.alg = zonefile_record.alg.to_i
|
292
|
+
self.fptype = zonefile_record.fptype.to_i
|
293
|
+
self.fp = zonefile_record.fp.to_s
|
294
|
+
self.comment = zonefile_record.comment&.to_s
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
class TXT < Record
|
300
|
+
attr_accessor :host, :data
|
301
|
+
|
302
|
+
def initialize(vars, zonefile_record)
|
303
|
+
@vars = vars
|
304
|
+
if zonefile_record
|
305
|
+
self.host = qualify_host(zonefile_record.host.to_s)
|
306
|
+
@vars[:last_host] = host
|
307
|
+
self.ttl = zonefile_record.ttl.to_i
|
308
|
+
self.klass = zonefile_record.klass.to_s
|
309
|
+
self.data = zonefile_record.data.to_s
|
310
|
+
self.comment = zonefile_record.comment&.to_s
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
class SPF < TXT
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
@@ -0,0 +1,586 @@
|
|
1
|
+
grammar Zonefile
|
2
|
+
rule zone
|
3
|
+
(variable / space_or_break / comment)* soa (resource_record / variable / comment / space / linebreak)* {
|
4
|
+
def variables
|
5
|
+
@variables ||= begin
|
6
|
+
raw = elements[0].elements.select { |e| e.to_s =~ /^\$/ }
|
7
|
+
variables = {}
|
8
|
+
raw.each do |e|
|
9
|
+
variables[e.name.text_value.to_s] = e.value.text_value.to_s
|
10
|
+
end
|
11
|
+
variables
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def origin
|
16
|
+
soa.origin.host.to_s
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
text_value
|
21
|
+
end
|
22
|
+
|
23
|
+
def rr
|
24
|
+
elements[-1].elements.select { |e| e.to_s !~ /\A\s*([;$].*)?\z|\A\z/; }
|
25
|
+
end
|
26
|
+
|
27
|
+
def entries
|
28
|
+
elements[0].elements.select { |e| e.to_s !~ /\A\s*(;.*)?\z|\A\z/; } +
|
29
|
+
[soa] +
|
30
|
+
elements[-1].elements.select { |e| e.to_s !~ /\A\s*(;.*)?\z|\A\z/; }
|
31
|
+
end
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
rule variable
|
36
|
+
"$" name:([a-zA-Z0-9]+) space value:([a-zA-Z0-9\.\-_]+) space? comment? {
|
37
|
+
def to_s
|
38
|
+
"$#{name.text_value.to_s} #{value.text_value.to_s}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def parse_type ; :variable ; end
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
rule soa
|
46
|
+
(
|
47
|
+
origin space ttl klass "SOA" space ns space "("? multiline_comment* space_or_break* rp multiline_comment* space_or_break* "("? multiline_comment* space_or_break* serial multiline_comment* space_or_break refresh multiline_comment* space_or_break reretry multiline_comment* space_or_break expiry multiline_comment* space_or_break nxttl multiline_comment* space_or_break* ")"? /
|
48
|
+
origin space klass ttl "SOA" space ns space "("? multiline_comment* space_or_break* rp multiline_comment* space_or_break* "("? multiline_comment* space_or_break* serial multiline_comment* space_or_break refresh multiline_comment* space_or_break reretry multiline_comment* space_or_break expiry multiline_comment* space_or_break nxttl multiline_comment* space_or_break* ")"?
|
49
|
+
) {
|
50
|
+
def to_s
|
51
|
+
"#{origin} #{ttl} #{klass} SOA #{ns} #{rp} (#{serial} #{refresh} #{reretry} #{expiry} #{nxttl})"
|
52
|
+
end
|
53
|
+
|
54
|
+
def parse_type ; :soa ; end
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
rule resource_record
|
59
|
+
record:(a_record / aaaa_record / caa_record / cname_record / mx_record / naptr_record / ns_record / ptr_record / srv_record / spf_record / sshfp_record / txt_record / soa_record) space* comment:comment? linebreak {
|
60
|
+
def zone
|
61
|
+
p = parent
|
62
|
+
while p.respond_to?(:parent) && p.parent
|
63
|
+
p = p.parent
|
64
|
+
end
|
65
|
+
p
|
66
|
+
end
|
67
|
+
|
68
|
+
def to_s
|
69
|
+
text_value
|
70
|
+
end
|
71
|
+
|
72
|
+
def record_type
|
73
|
+
record.record_type
|
74
|
+
end
|
75
|
+
|
76
|
+
def ttl
|
77
|
+
record.ttl || zone.variables['TTL'].to_i
|
78
|
+
end
|
79
|
+
|
80
|
+
def comment
|
81
|
+
return if super.empty?
|
82
|
+
super
|
83
|
+
end
|
84
|
+
|
85
|
+
def method_missing(method_name, *args)
|
86
|
+
if record.respond_to?(method_name)
|
87
|
+
record.send(method_name, *args)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def respond_to?(method_name)
|
92
|
+
super || record.respond_to?(method_name)
|
93
|
+
end
|
94
|
+
|
95
|
+
def parse_type ; :record ; end
|
96
|
+
}
|
97
|
+
end
|
98
|
+
|
99
|
+
rule a_record
|
100
|
+
(
|
101
|
+
host space ms_age ttl klass "A" space ip_address /
|
102
|
+
host space ms_age klass ttl "A" space ip_address
|
103
|
+
) {
|
104
|
+
def to_s
|
105
|
+
"#{host} #{ms_age} #{ttl} #{klass} A #{ip_address}"
|
106
|
+
end
|
107
|
+
|
108
|
+
def record_type
|
109
|
+
"A"
|
110
|
+
end
|
111
|
+
}
|
112
|
+
end
|
113
|
+
|
114
|
+
rule ip_address
|
115
|
+
[\d]+ "." [\d]+ "." [\d]+ "." [\d]+ {
|
116
|
+
def to_s
|
117
|
+
text_value
|
118
|
+
end
|
119
|
+
}
|
120
|
+
end
|
121
|
+
|
122
|
+
rule aaaa_record
|
123
|
+
(
|
124
|
+
host space ms_age ttl klass "AAAA" space ip_address:ip6_address /
|
125
|
+
host space ms_age klass ttl "AAAA" space ip_address:ip6_address
|
126
|
+
) {
|
127
|
+
def to_s
|
128
|
+
"#{host} #{ttl} #{klass} AAAA #{ip_address}"
|
129
|
+
end
|
130
|
+
|
131
|
+
def record_type
|
132
|
+
"AAAA"
|
133
|
+
end
|
134
|
+
}
|
135
|
+
end
|
136
|
+
|
137
|
+
rule ip6_address
|
138
|
+
[\da-fA-F:.] 2..39 {
|
139
|
+
def to_s
|
140
|
+
text_value.downcase
|
141
|
+
end
|
142
|
+
}
|
143
|
+
end
|
144
|
+
|
145
|
+
rule caa_record
|
146
|
+
(
|
147
|
+
host space ms_age ttl klass "CAA" space flags:integer space tag:unquoted_string space value:caa_value /
|
148
|
+
host space ms_age klass ttl "CAA" space flags:integer space tag:unquoted_string space value:caa_value
|
149
|
+
) {
|
150
|
+
def to_s
|
151
|
+
"#{host} #{ttl} #{klass} CAA #{flags} #{tag} #{value}"
|
152
|
+
end
|
153
|
+
|
154
|
+
def record_type
|
155
|
+
"CAA"
|
156
|
+
end
|
157
|
+
}
|
158
|
+
end
|
159
|
+
|
160
|
+
rule caa_value
|
161
|
+
(quoted_string / unquoted_string) {
|
162
|
+
def to_s
|
163
|
+
text_value
|
164
|
+
end
|
165
|
+
}
|
166
|
+
end
|
167
|
+
|
168
|
+
rule cname_record
|
169
|
+
(
|
170
|
+
host space ms_age ttl klass "CNAME" space target:host /
|
171
|
+
host space klass ms_age ttl "CNAME" space target:host /
|
172
|
+
host space ms_age ttl "CNAME" space target:host /
|
173
|
+
host space klass "CNAME" space target:host
|
174
|
+
) {
|
175
|
+
def to_s
|
176
|
+
"#{host} #{ttl} #{klass} CNAME #{target}"
|
177
|
+
end
|
178
|
+
|
179
|
+
def record_type
|
180
|
+
"CNAME"
|
181
|
+
end
|
182
|
+
}
|
183
|
+
end
|
184
|
+
|
185
|
+
rule mx_record
|
186
|
+
(
|
187
|
+
host space ttl klass "MX" space priority:integer space exchanger:host /
|
188
|
+
host space klass ttl "MX" space priority:integer space exchanger:host
|
189
|
+
) {
|
190
|
+
def to_s
|
191
|
+
"#{host} #{ttl} #{klass} MX #{priority} #{exchanger}"
|
192
|
+
end
|
193
|
+
|
194
|
+
def record_type
|
195
|
+
"MX"
|
196
|
+
end
|
197
|
+
}
|
198
|
+
end
|
199
|
+
|
200
|
+
rule naptr_record
|
201
|
+
(
|
202
|
+
host space ms_age ttl klass "NAPTR" space data /
|
203
|
+
host space ms_age klass ttl "NAPTR" space data
|
204
|
+
) {
|
205
|
+
def to_s
|
206
|
+
"#{host} #{ttl} #{klass} NAPTR #{data}"
|
207
|
+
end
|
208
|
+
|
209
|
+
def record_type
|
210
|
+
"NAPTR"
|
211
|
+
end
|
212
|
+
}
|
213
|
+
end
|
214
|
+
|
215
|
+
rule ns_record
|
216
|
+
(
|
217
|
+
host space ms_age ttl klass "NS" space nameserver:host /
|
218
|
+
host space ms_age klass ttl "NS" space nameserver:host
|
219
|
+
) {
|
220
|
+
def to_s
|
221
|
+
"#{host} #{ttl} #{klass} NS #{nameserver}"
|
222
|
+
end
|
223
|
+
|
224
|
+
def record_type
|
225
|
+
"NS"
|
226
|
+
end
|
227
|
+
}
|
228
|
+
end
|
229
|
+
|
230
|
+
rule ptr_record
|
231
|
+
(
|
232
|
+
host space ms_age ttl klass "PTR" space target:host /
|
233
|
+
host space ms_age klass ttl "PTR" space target:host
|
234
|
+
) {
|
235
|
+
def to_s
|
236
|
+
"#{host} #{ttl} #{klass} PTR #{target}"
|
237
|
+
end
|
238
|
+
|
239
|
+
def record_type
|
240
|
+
"PTR"
|
241
|
+
end
|
242
|
+
}
|
243
|
+
end
|
244
|
+
|
245
|
+
rule soa_record
|
246
|
+
(
|
247
|
+
origin space ms_age ttl klass "SOA" space ns space rp space data /
|
248
|
+
origin space ms_age klass ttl "SOA" space ns space rp space data
|
249
|
+
) {
|
250
|
+
def to_s
|
251
|
+
"#{origin} #{ttl} #{klass} SOA #{ns} #{rp} (#{space})"
|
252
|
+
end
|
253
|
+
|
254
|
+
def record_type
|
255
|
+
"SOA"
|
256
|
+
end
|
257
|
+
}
|
258
|
+
end
|
259
|
+
|
260
|
+
rule srv_record
|
261
|
+
(
|
262
|
+
host space ms_age ttl klass "SRV" space priority:integer space weight:integer space port:integer space target:host /
|
263
|
+
host space klass ms_age ttl "SRV" space priority:integer space weight:integer space port:integer space target:host /
|
264
|
+
host space ms_age ttl "SRV" space priority:integer space weight:integer space port:integer space target:host /
|
265
|
+
host space klass "SRV" space priority:integer space weight:integer space port:integer space target:host
|
266
|
+
) {
|
267
|
+
def to_s
|
268
|
+
"#{host} #{ttl} #{klass} SRV #{priority} #{weight} #{port} #{target}"
|
269
|
+
end
|
270
|
+
|
271
|
+
def record_type
|
272
|
+
"SRV"
|
273
|
+
end
|
274
|
+
}
|
275
|
+
end
|
276
|
+
|
277
|
+
rule spf_record
|
278
|
+
(
|
279
|
+
host space ms_age ttl klass "SPF" space data:txt_data /
|
280
|
+
host space ms_age klass ttl "SPF" space data:txt_data
|
281
|
+
) {
|
282
|
+
def to_s
|
283
|
+
"#{host} #{ttl} #{klass} SPF #{data}"
|
284
|
+
end
|
285
|
+
|
286
|
+
def record_type
|
287
|
+
"SPF"
|
288
|
+
end
|
289
|
+
}
|
290
|
+
end
|
291
|
+
|
292
|
+
rule sshfp_record
|
293
|
+
(
|
294
|
+
host space ms_age ttl klass "SSHFP" space alg:integer space fptype:integer space fp:fingerprint /
|
295
|
+
host space ms_age klass ttl "SSHFP" space alg:integer space fptype:integer space fp:fingerprint
|
296
|
+
) {
|
297
|
+
def to_s
|
298
|
+
"#{host} #{ttl} #{klass} SSHFP #{alg} #{fptype} #{fp}"
|
299
|
+
end
|
300
|
+
|
301
|
+
def record_type
|
302
|
+
"SSHFP"
|
303
|
+
end
|
304
|
+
}
|
305
|
+
end
|
306
|
+
|
307
|
+
rule txt_record
|
308
|
+
(
|
309
|
+
host space ms_age ttl klass "TXT" space data:ms_txt_data /
|
310
|
+
host space ms_age klass ttl "TXT" space data:ms_txt_data
|
311
|
+
) {
|
312
|
+
def to_s
|
313
|
+
"#{host} #{ttl} #{klass} TXT #{data}"
|
314
|
+
end
|
315
|
+
|
316
|
+
def record_type
|
317
|
+
"TXT"
|
318
|
+
end
|
319
|
+
}
|
320
|
+
end
|
321
|
+
|
322
|
+
rule origin
|
323
|
+
host comment* {
|
324
|
+
def to_s
|
325
|
+
"#{host}"
|
326
|
+
end
|
327
|
+
}
|
328
|
+
end
|
329
|
+
|
330
|
+
rule multiline_comment
|
331
|
+
linebreak+ comment* {
|
332
|
+
def to_s
|
333
|
+
text_value.strip
|
334
|
+
end
|
335
|
+
}
|
336
|
+
end
|
337
|
+
|
338
|
+
rule space
|
339
|
+
[ \t]+ {
|
340
|
+
def to_s
|
341
|
+
text_value
|
342
|
+
end
|
343
|
+
}
|
344
|
+
end
|
345
|
+
|
346
|
+
rule linebreak
|
347
|
+
[\n\r]+ {
|
348
|
+
def to_s
|
349
|
+
''
|
350
|
+
end
|
351
|
+
}
|
352
|
+
end
|
353
|
+
|
354
|
+
rule space_or_break
|
355
|
+
[\s]+ {
|
356
|
+
def to_s
|
357
|
+
text_value
|
358
|
+
end
|
359
|
+
}
|
360
|
+
end
|
361
|
+
|
362
|
+
rule klass
|
363
|
+
(("IN" space) / '') {
|
364
|
+
def to_s
|
365
|
+
text_value.strip
|
366
|
+
end
|
367
|
+
}
|
368
|
+
end
|
369
|
+
|
370
|
+
rule comment
|
371
|
+
space* ";" [^\n\r]* {
|
372
|
+
def to_s
|
373
|
+
text_value[2..].to_s.strip
|
374
|
+
end
|
375
|
+
|
376
|
+
def parse_type ; :comment ; end
|
377
|
+
}
|
378
|
+
end
|
379
|
+
|
380
|
+
rule ns
|
381
|
+
host comment* {
|
382
|
+
def to_s
|
383
|
+
"#{host}"
|
384
|
+
end
|
385
|
+
}
|
386
|
+
end
|
387
|
+
|
388
|
+
rule rp
|
389
|
+
rp_value comment* {
|
390
|
+
def to_s
|
391
|
+
"#{rp_value}"
|
392
|
+
end
|
393
|
+
}
|
394
|
+
end
|
395
|
+
|
396
|
+
rule rp_value
|
397
|
+
("." / (("\\." / [a-zA-Z0-9\-)])+ "."?)+) {
|
398
|
+
def to_s
|
399
|
+
text_value
|
400
|
+
end
|
401
|
+
}
|
402
|
+
end
|
403
|
+
|
404
|
+
rule serial
|
405
|
+
integer comment* {
|
406
|
+
def to_i
|
407
|
+
integer.to_i
|
408
|
+
end
|
409
|
+
def to_s
|
410
|
+
"#{to_i}"
|
411
|
+
end
|
412
|
+
}
|
413
|
+
end
|
414
|
+
|
415
|
+
rule time_interval
|
416
|
+
integer time_multiplier {
|
417
|
+
def to_s
|
418
|
+
text_value
|
419
|
+
end
|
420
|
+
|
421
|
+
def to_i
|
422
|
+
time_multiplier.to_i * integer.to_i
|
423
|
+
end
|
424
|
+
}
|
425
|
+
end
|
426
|
+
|
427
|
+
rule refresh
|
428
|
+
time_interval comment* {
|
429
|
+
def to_i
|
430
|
+
time_interval.to_i
|
431
|
+
end
|
432
|
+
def to_s
|
433
|
+
time_interval.to_s
|
434
|
+
end
|
435
|
+
}
|
436
|
+
end
|
437
|
+
|
438
|
+
rule integer
|
439
|
+
[0-9]+ {
|
440
|
+
def to_i
|
441
|
+
text_value.to_i
|
442
|
+
end
|
443
|
+
def to_s
|
444
|
+
"#{to_i}"
|
445
|
+
end
|
446
|
+
}
|
447
|
+
end
|
448
|
+
|
449
|
+
rule time_multiplier
|
450
|
+
( 's' / 'S' / 'm' / 'M' / 'h' / 'H' / 'd' / 'D' / 'w' / 'W' / '' ) {
|
451
|
+
def to_s
|
452
|
+
text_value
|
453
|
+
end
|
454
|
+
def to_i
|
455
|
+
case text_value.downcase
|
456
|
+
when 'm' then 60
|
457
|
+
when 'h' then 60 * 60
|
458
|
+
when 'd' then 60 * 60 * 24
|
459
|
+
when 'w' then 60 * 60 * 24 * 7
|
460
|
+
else
|
461
|
+
1
|
462
|
+
end
|
463
|
+
end
|
464
|
+
}
|
465
|
+
end
|
466
|
+
|
467
|
+
rule reretry
|
468
|
+
time_interval comment* {
|
469
|
+
def to_i
|
470
|
+
time_interval.to_i
|
471
|
+
end
|
472
|
+
def to_s
|
473
|
+
time_interval.to_s
|
474
|
+
end
|
475
|
+
}
|
476
|
+
end
|
477
|
+
|
478
|
+
rule expiry
|
479
|
+
time_interval comment* {
|
480
|
+
def to_i
|
481
|
+
time_interval.to_i
|
482
|
+
end
|
483
|
+
def to_s
|
484
|
+
time_interval.to_s
|
485
|
+
end
|
486
|
+
}
|
487
|
+
end
|
488
|
+
|
489
|
+
rule nxttl
|
490
|
+
time_interval comment* {
|
491
|
+
def to_i
|
492
|
+
time_interval.to_i
|
493
|
+
end
|
494
|
+
def to_s
|
495
|
+
time_interval.to_s
|
496
|
+
end
|
497
|
+
}
|
498
|
+
end
|
499
|
+
|
500
|
+
rule ms_age
|
501
|
+
( "[AGE:" [\d]+ "]" space / '' ) {
|
502
|
+
def to_s
|
503
|
+
text_value
|
504
|
+
end
|
505
|
+
}
|
506
|
+
end
|
507
|
+
|
508
|
+
rule ttl
|
509
|
+
((time_interval space) / '') {
|
510
|
+
def to_i
|
511
|
+
respond_to?(:time_interval) ? time_interval.to_i : nil
|
512
|
+
end
|
513
|
+
def to_s
|
514
|
+
respond_to?(:time_interval) ? time_interval.to_s : ''
|
515
|
+
end
|
516
|
+
}
|
517
|
+
end
|
518
|
+
|
519
|
+
rule host
|
520
|
+
( ([*a-zA-Z0-9\-\._]+) / "@" / ' ' / "\t" ) {
|
521
|
+
def to_s
|
522
|
+
text_value
|
523
|
+
end
|
524
|
+
}
|
525
|
+
end
|
526
|
+
|
527
|
+
rule fingerprint
|
528
|
+
[a-fA-Z0-9:]+ {
|
529
|
+
def to_s
|
530
|
+
text_value.strip
|
531
|
+
end
|
532
|
+
}
|
533
|
+
end
|
534
|
+
|
535
|
+
rule data
|
536
|
+
[^;\n\r]+ {
|
537
|
+
def to_s
|
538
|
+
text_value.strip
|
539
|
+
end
|
540
|
+
}
|
541
|
+
end
|
542
|
+
|
543
|
+
rule ms_txt_data
|
544
|
+
(
|
545
|
+
"(" space* data:txt_data space* ")" /
|
546
|
+
data:txt_data
|
547
|
+
) {
|
548
|
+
def to_s
|
549
|
+
data.to_s
|
550
|
+
end
|
551
|
+
}
|
552
|
+
end
|
553
|
+
|
554
|
+
rule txt_data
|
555
|
+
txt_string (space txt_data)* {
|
556
|
+
def to_s
|
557
|
+
text_value
|
558
|
+
end
|
559
|
+
}
|
560
|
+
end
|
561
|
+
|
562
|
+
rule txt_string
|
563
|
+
(quoted_string / unquoted_string) {
|
564
|
+
def to_s
|
565
|
+
text_value
|
566
|
+
end
|
567
|
+
}
|
568
|
+
end
|
569
|
+
|
570
|
+
rule quoted_string
|
571
|
+
( '"' ( '\"' / [^"] )* '"') {
|
572
|
+
def to_s
|
573
|
+
text_value
|
574
|
+
end
|
575
|
+
}
|
576
|
+
end
|
577
|
+
|
578
|
+
rule unquoted_string
|
579
|
+
'[a-zA-Z0-9=_\.\-\@\:\~]+'r {
|
580
|
+
def to_s
|
581
|
+
text_value
|
582
|
+
end
|
583
|
+
}
|
584
|
+
end
|
585
|
+
|
586
|
+
end
|
data/zonesync.gemspec
CHANGED
@@ -27,9 +27,9 @@ Gem::Specification.new do |spec|
|
|
27
27
|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
28
28
|
spec.require_paths = ["lib"]
|
29
29
|
|
30
|
-
spec.add_dependency "dns-zonefile", "~>1.0"
|
31
30
|
spec.add_dependency "diff-lcs", "~>1.4"
|
32
31
|
spec.add_dependency "thor", "~>1.0"
|
32
|
+
spec.add_dependency "treetop", "~>1.6"
|
33
33
|
|
34
34
|
spec.add_development_dependency "rake"
|
35
35
|
spec.add_development_dependency "rspec"
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: zonesync
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Micah Geisel
|
@@ -9,50 +9,50 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date: 2024-
|
12
|
+
date: 2024-08-23 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
|
-
name:
|
15
|
+
name: diff-lcs
|
16
16
|
requirement: !ruby/object:Gem::Requirement
|
17
17
|
requirements:
|
18
18
|
- - "~>"
|
19
19
|
- !ruby/object:Gem::Version
|
20
|
-
version: '1.
|
20
|
+
version: '1.4'
|
21
21
|
type: :runtime
|
22
22
|
prerelease: false
|
23
23
|
version_requirements: !ruby/object:Gem::Requirement
|
24
24
|
requirements:
|
25
25
|
- - "~>"
|
26
26
|
- !ruby/object:Gem::Version
|
27
|
-
version: '1.
|
27
|
+
version: '1.4'
|
28
28
|
- !ruby/object:Gem::Dependency
|
29
|
-
name:
|
29
|
+
name: thor
|
30
30
|
requirement: !ruby/object:Gem::Requirement
|
31
31
|
requirements:
|
32
32
|
- - "~>"
|
33
33
|
- !ruby/object:Gem::Version
|
34
|
-
version: '1.
|
34
|
+
version: '1.0'
|
35
35
|
type: :runtime
|
36
36
|
prerelease: false
|
37
37
|
version_requirements: !ruby/object:Gem::Requirement
|
38
38
|
requirements:
|
39
39
|
- - "~>"
|
40
40
|
- !ruby/object:Gem::Version
|
41
|
-
version: '1.
|
41
|
+
version: '1.0'
|
42
42
|
- !ruby/object:Gem::Dependency
|
43
|
-
name:
|
43
|
+
name: treetop
|
44
44
|
requirement: !ruby/object:Gem::Requirement
|
45
45
|
requirements:
|
46
46
|
- - "~>"
|
47
47
|
- !ruby/object:Gem::Version
|
48
|
-
version: '1.
|
48
|
+
version: '1.6'
|
49
49
|
type: :runtime
|
50
50
|
prerelease: false
|
51
51
|
version_requirements: !ruby/object:Gem::Requirement
|
52
52
|
requirements:
|
53
53
|
- - "~>"
|
54
54
|
- !ruby/object:Gem::Version
|
55
|
-
version: '1.
|
55
|
+
version: '1.6'
|
56
56
|
- !ruby/object:Gem::Dependency
|
57
57
|
name: rake
|
58
58
|
requirement: !ruby/object:Gem::Requirement
|
@@ -121,6 +121,8 @@ files:
|
|
121
121
|
- lib/zonesync/rake.rb
|
122
122
|
- lib/zonesync/record.rb
|
123
123
|
- lib/zonesync/version.rb
|
124
|
+
- lib/zonesync/zonefile.rb
|
125
|
+
- lib/zonesync/zonefile.treetop
|
124
126
|
- log/.keep
|
125
127
|
- zonesync.gemspec
|
126
128
|
homepage: https://github.com/botandrose/zonesync
|