zonesync 0.9.0 → 0.10.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/Gemfile +1 -0
- data/lib/zonesync/cli.rb +9 -0
- data/lib/zonesync/cloudflare.rb +64 -39
- data/lib/zonesync/diff.rb +10 -1
- data/lib/zonesync/errors.rb +35 -0
- data/lib/zonesync/generate.rb +14 -0
- data/lib/zonesync/http.rb +24 -8
- data/lib/zonesync/logger.rb +20 -15
- data/lib/zonesync/manifest.rb +21 -5
- data/lib/zonesync/parser.rb +337 -0
- data/lib/zonesync/provider.rb +80 -26
- data/lib/zonesync/record.rb +18 -23
- data/lib/zonesync/route53.rb +48 -27
- data/lib/zonesync/sync.rb +40 -0
- data/lib/zonesync/validator.rb +19 -15
- data/lib/zonesync/version.rb +1 -1
- data/lib/zonesync/zonefile.rb +22 -311
- data/lib/zonesync.rb +27 -60
- data/sorbet/config +4 -0
- data/sorbet/rbi/annotations/.gitattributes +1 -0
- data/sorbet/rbi/annotations/activesupport.rbi +457 -0
- data/sorbet/rbi/annotations/minitest.rbi +119 -0
- data/sorbet/rbi/annotations/webmock.rbi +9 -0
- data/sorbet/rbi/gems/.gitattributes +1 -0
- data/sorbet/rbi/gems/activesupport@8.0.1.rbi +18474 -0
- data/sorbet/rbi/gems/addressable@2.8.7.rbi +1994 -0
- data/sorbet/rbi/gems/base64@0.2.0.rbi +507 -0
- data/sorbet/rbi/gems/benchmark@0.4.0.rbi +618 -0
- data/sorbet/rbi/gems/bigdecimal@3.1.9.rbi +9 -0
- data/sorbet/rbi/gems/concurrent-ruby@1.3.4.rbi +11645 -0
- data/sorbet/rbi/gems/connection_pool@2.4.1.rbi +9 -0
- data/sorbet/rbi/gems/crack@1.0.0.rbi +145 -0
- data/sorbet/rbi/gems/date@3.4.1.rbi +75 -0
- data/sorbet/rbi/gems/diff-lcs@1.5.1.rbi +1131 -0
- data/sorbet/rbi/gems/drb@2.2.1.rbi +1347 -0
- data/sorbet/rbi/gems/erubi@1.13.1.rbi +155 -0
- data/sorbet/rbi/gems/hashdiff@1.1.2.rbi +353 -0
- data/sorbet/rbi/gems/i18n@1.14.6.rbi +2275 -0
- data/sorbet/rbi/gems/io-console@0.8.0.rbi +9 -0
- data/sorbet/rbi/gems/logger@1.6.4.rbi +940 -0
- data/sorbet/rbi/gems/minitest@5.25.4.rbi +1547 -0
- data/sorbet/rbi/gems/netrc@0.11.0.rbi +159 -0
- data/sorbet/rbi/gems/parallel@1.26.3.rbi +291 -0
- data/sorbet/rbi/gems/polyglot@0.3.5.rbi +42 -0
- data/sorbet/rbi/gems/prism@1.3.0.rbi +40040 -0
- data/sorbet/rbi/gems/psych@5.2.2.rbi +1785 -0
- data/sorbet/rbi/gems/public_suffix@6.0.1.rbi +936 -0
- data/sorbet/rbi/gems/rake@13.2.1.rbi +3028 -0
- data/sorbet/rbi/gems/rbi@0.2.2.rbi +4527 -0
- data/sorbet/rbi/gems/rdoc@6.10.0.rbi +12766 -0
- data/sorbet/rbi/gems/reline@0.6.0.rbi +9 -0
- data/sorbet/rbi/gems/rexml@3.4.0.rbi +4974 -0
- data/sorbet/rbi/gems/rspec-core@3.13.2.rbi +10896 -0
- data/sorbet/rbi/gems/rspec-expectations@3.13.3.rbi +8183 -0
- data/sorbet/rbi/gems/rspec-mocks@3.13.2.rbi +5341 -0
- data/sorbet/rbi/gems/rspec-support@3.13.2.rbi +1630 -0
- data/sorbet/rbi/gems/rspec@3.13.0.rbi +83 -0
- data/sorbet/rbi/gems/securerandom@0.4.1.rbi +75 -0
- data/sorbet/rbi/gems/spoom@1.5.0.rbi +4932 -0
- data/sorbet/rbi/gems/stringio@3.1.2.rbi +9 -0
- data/sorbet/rbi/gems/tapioca@0.16.6.rbi +3611 -0
- data/sorbet/rbi/gems/thor@1.3.2.rbi +4378 -0
- data/sorbet/rbi/gems/treetop@1.6.12.rbi +1895 -0
- data/sorbet/rbi/gems/tzinfo@2.0.6.rbi +5918 -0
- data/sorbet/rbi/gems/uri@1.0.2.rbi +2340 -0
- data/sorbet/rbi/gems/webmock@3.24.0.rbi +1780 -0
- data/sorbet/rbi/gems/yard-sorbet@0.9.0.rbi +435 -0
- data/sorbet/rbi/gems/yard@0.9.37.rbi +18379 -0
- data/sorbet/rbi/todo.rbi +7 -0
- data/sorbet/tapioca/config.yml +13 -0
- data/sorbet/tapioca/require.rb +4 -0
- data/zonesync.gemspec +3 -0
- metadata +100 -2
@@ -0,0 +1,337 @@
|
|
1
|
+
# typed: true
|
2
|
+
require "sorbet-runtime"
|
3
|
+
|
4
|
+
require "treetop"
|
5
|
+
Treetop.load File.join(__dir__, "zonefile")
|
6
|
+
|
7
|
+
module Zonesync
|
8
|
+
class Parser
|
9
|
+
def self.parse(zone_string)
|
10
|
+
parser = T.unsafe(ZonefileParser).new
|
11
|
+
result = parser.parse(zone_string)
|
12
|
+
if !result
|
13
|
+
puts zone_string
|
14
|
+
raise ParsingError, parser.failure_reason
|
15
|
+
end
|
16
|
+
origin = result.variables["ORIGIN"]
|
17
|
+
Zone.new(result.entries, origin: origin)
|
18
|
+
end
|
19
|
+
|
20
|
+
class ParsingError < RuntimeError; end
|
21
|
+
class UnknownRecordType < RuntimeError; end
|
22
|
+
|
23
|
+
class Zone
|
24
|
+
attr_reader :origin
|
25
|
+
|
26
|
+
attr_reader :records
|
27
|
+
|
28
|
+
def initialize(entries, origin: nil, alternate_origin: ".")
|
29
|
+
@origin = origin
|
30
|
+
@records = []
|
31
|
+
@vars = {"origin" => alternate_origin, :last_host => "."}
|
32
|
+
entries.each do |e|
|
33
|
+
case e.parse_type
|
34
|
+
when :variable
|
35
|
+
key = e.name.text_value.downcase
|
36
|
+
@vars[key] = case key
|
37
|
+
when "ttl"
|
38
|
+
e.value.text_value.to_i
|
39
|
+
else
|
40
|
+
e.value.text_value
|
41
|
+
end
|
42
|
+
when :soa
|
43
|
+
@records << SOA.new(@vars, e)
|
44
|
+
when :record
|
45
|
+
case e.record_type
|
46
|
+
when "A" then @records << A.new(@vars, e)
|
47
|
+
when "AAAA" then @records << AAAA.new(@vars, e)
|
48
|
+
when "CAA" then @records << CAA.new(@vars, e)
|
49
|
+
when "CNAME" then @records << CNAME.new(@vars, e)
|
50
|
+
when "MX" then @records << MX.new(@vars, e)
|
51
|
+
when "NAPTR" then @records << NAPTR.new(@vars, e)
|
52
|
+
when "NS" then @records << NS.new(@vars, e)
|
53
|
+
when "PTR" then @records << PTR.new(@vars, e)
|
54
|
+
when "SRV" then @records << SRV.new(@vars, e)
|
55
|
+
when "SPF" then @records << SPF.new(@vars, e)
|
56
|
+
when "SSHFP" then @records << SSHFP.new(@vars, e)
|
57
|
+
when "TXT" then @records << TXT.new(@vars, e)
|
58
|
+
when "SOA" then
|
59
|
+
# No-op
|
60
|
+
else
|
61
|
+
raise UnknownRecordType, "Unknown record type: #{e.record_type}"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
@origin ||= soa.origin
|
66
|
+
end
|
67
|
+
|
68
|
+
def soa
|
69
|
+
records_of(SOA).first
|
70
|
+
end
|
71
|
+
|
72
|
+
def records_of(kl)
|
73
|
+
@records.select { |r| r.instance_of? kl }
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class Record
|
78
|
+
attr_reader :ttl
|
79
|
+
def ttl= val
|
80
|
+
# handling for global TTL
|
81
|
+
@ttl = val || @vars["ttl"]
|
82
|
+
end
|
83
|
+
|
84
|
+
def klass
|
85
|
+
@klass = nil if @klass == ""
|
86
|
+
@klass ||= "IN"
|
87
|
+
end
|
88
|
+
attr_writer :klass
|
89
|
+
|
90
|
+
attr_accessor :comment, :host
|
91
|
+
|
92
|
+
def type
|
93
|
+
T.must(self.class.name).split("::").last
|
94
|
+
end
|
95
|
+
|
96
|
+
attr_reader :rdata
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
def qualify_host(host)
|
101
|
+
origin = vars["origin"]
|
102
|
+
host = vars[:last_host] if /^\s*$/.match?(host)
|
103
|
+
host = host.gsub(/@/, origin)
|
104
|
+
if /\.$/.match?(host)
|
105
|
+
host
|
106
|
+
elsif /^\./.match?(origin)
|
107
|
+
host + origin
|
108
|
+
else
|
109
|
+
host + "." + origin
|
110
|
+
end
|
111
|
+
end
|
112
|
+
attr_accessor :vars
|
113
|
+
end
|
114
|
+
|
115
|
+
class SOA < Record
|
116
|
+
attr_accessor :origin, :nameserver, :responsible_party, :serial, :refresh_time, :retry_time, :expiry_time, :nxttl
|
117
|
+
|
118
|
+
def initialize(vars, zonefile_soa = nil)
|
119
|
+
@vars = vars
|
120
|
+
if zonefile_soa
|
121
|
+
self.origin = qualify_host(zonefile_soa.origin.to_s)
|
122
|
+
@vars[:last_host] = origin
|
123
|
+
self.ttl = zonefile_soa.ttl.to_i
|
124
|
+
self.klass = zonefile_soa.klass.to_s
|
125
|
+
self.nameserver = qualify_host(zonefile_soa.ns.to_s)
|
126
|
+
self.responsible_party = qualify_host(zonefile_soa.rp.to_s)
|
127
|
+
self.serial = zonefile_soa.serial.to_i
|
128
|
+
self.refresh_time = zonefile_soa.refresh.to_i
|
129
|
+
self.retry_time = zonefile_soa.reretry.to_i
|
130
|
+
self.expiry_time = zonefile_soa.expiry.to_i
|
131
|
+
self.nxttl = zonefile_soa.nxttl.to_i
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
alias host origin
|
136
|
+
end
|
137
|
+
|
138
|
+
class A < Record
|
139
|
+
attr_accessor :address
|
140
|
+
|
141
|
+
def initialize(vars, zonefile_record)
|
142
|
+
@vars = vars
|
143
|
+
if zonefile_record
|
144
|
+
self.host = qualify_host(zonefile_record.host.to_s)
|
145
|
+
@vars[:last_host] = host
|
146
|
+
self.ttl = zonefile_record.ttl.to_i
|
147
|
+
self.klass = zonefile_record.klass.to_s
|
148
|
+
self.address = zonefile_record.ip_address.to_s
|
149
|
+
self.comment = zonefile_record.comment&.to_s
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
alias rdata address
|
154
|
+
end
|
155
|
+
|
156
|
+
class AAAA < A
|
157
|
+
end
|
158
|
+
|
159
|
+
class CAA < Record
|
160
|
+
attr_accessor :flags, :tag, :value
|
161
|
+
|
162
|
+
def initialize(vars, zonefile_record)
|
163
|
+
@vars = vars
|
164
|
+
if zonefile_record
|
165
|
+
self.host = qualify_host(zonefile_record.host.to_s)
|
166
|
+
@vars[:last_host] = host
|
167
|
+
self.ttl = zonefile_record.ttl.to_i
|
168
|
+
self.klass = zonefile_record.klass.to_s
|
169
|
+
self.flags = zonefile_record.flags.to_i
|
170
|
+
self.tag = zonefile_record.tag.to_s
|
171
|
+
self.value = zonefile_record.value.to_s
|
172
|
+
self.comment = zonefile_record.comment&.to_s
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
class CNAME < Record
|
178
|
+
attr_accessor :domainname
|
179
|
+
|
180
|
+
def initialize(vars, zonefile_record)
|
181
|
+
@vars = vars
|
182
|
+
if zonefile_record
|
183
|
+
self.host = qualify_host(zonefile_record.host.to_s)
|
184
|
+
@vars[:last_host] = host
|
185
|
+
self.ttl = zonefile_record.ttl.to_i
|
186
|
+
self.klass = zonefile_record.klass.to_s
|
187
|
+
self.domainname = qualify_host(zonefile_record.target.to_s)
|
188
|
+
self.comment = zonefile_record.comment&.to_s
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
alias target domainname
|
193
|
+
alias rdata domainname
|
194
|
+
alias alias domainname
|
195
|
+
end
|
196
|
+
|
197
|
+
class MX < Record
|
198
|
+
attr_accessor :priority, :domainname
|
199
|
+
|
200
|
+
def initialize(vars, zonefile_record)
|
201
|
+
@vars = vars
|
202
|
+
if zonefile_record
|
203
|
+
self.host = qualify_host(zonefile_record.host.to_s)
|
204
|
+
@vars[:last_host] = host
|
205
|
+
self.ttl = zonefile_record.ttl.to_i
|
206
|
+
self.klass = zonefile_record.klass.to_s
|
207
|
+
self.priority = zonefile_record.priority.to_i
|
208
|
+
self.domainname = qualify_host(zonefile_record.exchanger.to_s)
|
209
|
+
self.comment = zonefile_record.comment&.to_s
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def rdata
|
214
|
+
"#{priority} #{domainname}"
|
215
|
+
end
|
216
|
+
|
217
|
+
alias exchange domainname
|
218
|
+
alias exchanger domainname
|
219
|
+
end
|
220
|
+
|
221
|
+
class NAPTR < Record
|
222
|
+
attr_accessor :data
|
223
|
+
|
224
|
+
def initialize(vars, zonefile_record)
|
225
|
+
@vars = vars
|
226
|
+
if zonefile_record
|
227
|
+
self.host = qualify_host(zonefile_record.host.to_s)
|
228
|
+
@vars[:last_host] = host
|
229
|
+
self.ttl = zonefile_record.ttl.to_i
|
230
|
+
self.klass = zonefile_record.klass.to_s
|
231
|
+
self.data = zonefile_record.data.to_s
|
232
|
+
self.comment = zonefile_record.comment&.to_s
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
alias rdata data
|
237
|
+
end
|
238
|
+
|
239
|
+
class NS < Record
|
240
|
+
attr_accessor :domainname
|
241
|
+
|
242
|
+
def initialize(vars, zonefile_record)
|
243
|
+
@vars = vars
|
244
|
+
if zonefile_record
|
245
|
+
self.host = qualify_host(zonefile_record.host.to_s)
|
246
|
+
@vars[:last_host] = host
|
247
|
+
self.ttl = zonefile_record.ttl.to_i
|
248
|
+
self.klass = zonefile_record.klass.to_s
|
249
|
+
self.domainname = qualify_host(zonefile_record.nameserver.to_s)
|
250
|
+
self.comment = zonefile_record.comment&.to_s
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
alias nameserver domainname
|
255
|
+
alias rdata domainname
|
256
|
+
end
|
257
|
+
|
258
|
+
class PTR < Record
|
259
|
+
attr_accessor :domainname
|
260
|
+
|
261
|
+
def initialize(vars, zonefile_record)
|
262
|
+
@vars = vars
|
263
|
+
if zonefile_record
|
264
|
+
self.host = qualify_host(zonefile_record.host.to_s)
|
265
|
+
@vars[:last_host] = host
|
266
|
+
self.ttl = zonefile_record.ttl.to_i
|
267
|
+
self.klass = zonefile_record.klass.to_s
|
268
|
+
self.domainname = qualify_host(zonefile_record.target.to_s)
|
269
|
+
self.comment = zonefile_record.comment&.to_s
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
alias target domainname
|
274
|
+
alias rdata domainname
|
275
|
+
end
|
276
|
+
|
277
|
+
class SRV < Record
|
278
|
+
attr_accessor :priority, :weight, :port, :domainname
|
279
|
+
|
280
|
+
def initialize(vars, zonefile_record)
|
281
|
+
@vars = vars
|
282
|
+
if zonefile_record
|
283
|
+
self.host = qualify_host(zonefile_record.host.to_s)
|
284
|
+
@vars[:last_host] = host
|
285
|
+
self.ttl = zonefile_record.ttl.to_i
|
286
|
+
self.klass = zonefile_record.klass.to_s
|
287
|
+
self.priority = zonefile_record.priority.to_i
|
288
|
+
self.weight = zonefile_record.weight.to_i
|
289
|
+
self.port = zonefile_record.port.to_i
|
290
|
+
self.domainname = qualify_host(zonefile_record.target.to_s)
|
291
|
+
self.comment = zonefile_record.comment&.to_s
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
alias target domainname
|
296
|
+
end
|
297
|
+
|
298
|
+
class SSHFP < Record
|
299
|
+
attr_accessor :alg, :fptype, :fp
|
300
|
+
|
301
|
+
def initialize(vars, zonefile_record)
|
302
|
+
@vars = vars
|
303
|
+
if zonefile_record
|
304
|
+
self.host = qualify_host(zonefile_record.host.to_s)
|
305
|
+
@vars[:last_host] = host
|
306
|
+
self.ttl = zonefile_record.ttl.to_i
|
307
|
+
self.klass = zonefile_record.klass.to_s
|
308
|
+
self.alg = zonefile_record.alg.to_i
|
309
|
+
self.fptype = zonefile_record.fptype.to_i
|
310
|
+
self.fp = zonefile_record.fp.to_s
|
311
|
+
self.comment = zonefile_record.comment&.to_s
|
312
|
+
end
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
class TXT < Record
|
317
|
+
attr_accessor :data
|
318
|
+
|
319
|
+
def initialize(vars, zonefile_record)
|
320
|
+
@vars = vars
|
321
|
+
if zonefile_record
|
322
|
+
self.host = qualify_host(zonefile_record.host.to_s)
|
323
|
+
@vars[:last_host] = host
|
324
|
+
self.ttl = zonefile_record.ttl.to_i
|
325
|
+
self.klass = zonefile_record.klass.to_s
|
326
|
+
self.data = zonefile_record.data.to_s
|
327
|
+
self.comment = zonefile_record.comment&.to_s
|
328
|
+
end
|
329
|
+
end
|
330
|
+
alias rdata data
|
331
|
+
end
|
332
|
+
|
333
|
+
class SPF < TXT
|
334
|
+
end
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
data/lib/zonesync/provider.rb
CHANGED
@@ -1,58 +1,102 @@
|
|
1
|
+
# typed: strict
|
2
|
+
require "sorbet-runtime"
|
3
|
+
|
1
4
|
require "zonesync/record"
|
2
5
|
require "zonesync/zonefile"
|
3
6
|
require "zonesync/manifest"
|
7
|
+
require "zonesync/diff"
|
8
|
+
require "zonesync/validator"
|
4
9
|
|
5
10
|
module Zonesync
|
6
|
-
class Provider
|
7
|
-
|
8
|
-
|
9
|
-
|
11
|
+
class Provider
|
12
|
+
extend T::Sig
|
13
|
+
|
14
|
+
sig { params(config: T::Hash[Symbol, String]).void }
|
15
|
+
def initialize config
|
16
|
+
@config = T.let(config, T::Hash[Symbol, String])
|
10
17
|
end
|
18
|
+
sig { returns(T::Hash[Symbol, String]) }
|
19
|
+
attr_reader :config
|
11
20
|
|
21
|
+
sig { params(config: T::Hash[Symbol, String]).returns(Provider) }
|
22
|
+
def self.from config
|
23
|
+
Zonesync.const_get(config.fetch(:provider)).new(config)
|
24
|
+
end
|
25
|
+
|
26
|
+
sig { params(other: Provider, force: T::Boolean).returns(T::Array[Operation]) }
|
27
|
+
def diff! other, force: false
|
28
|
+
operations = diff(other).call
|
29
|
+
Validator.call(operations, self, force: force)
|
30
|
+
operations
|
31
|
+
end
|
32
|
+
|
33
|
+
sig { params(other: Provider).returns(Diff) }
|
34
|
+
def diff other
|
35
|
+
Diff.new(
|
36
|
+
from: diffable_records,
|
37
|
+
to: other.diffable_records,
|
38
|
+
)
|
39
|
+
end
|
40
|
+
|
41
|
+
sig { returns(T::Array[Record]) }
|
12
42
|
def records
|
13
|
-
zonefile.records
|
14
|
-
Record.from_dns_zonefile_record(record)
|
15
|
-
end
|
43
|
+
zonefile.records
|
16
44
|
end
|
17
45
|
|
46
|
+
sig { returns(T::Array[Record]) }
|
18
47
|
def diffable_records
|
19
48
|
records.select do |record|
|
20
49
|
manifest.diffable?(record)
|
21
50
|
end.sort
|
22
51
|
end
|
23
52
|
|
53
|
+
sig { returns(Manifest) }
|
24
54
|
def manifest
|
25
|
-
|
55
|
+
Manifest.new(records, zonefile)
|
26
56
|
end
|
27
57
|
|
58
|
+
sig { returns(Zonefile) }
|
28
59
|
private def zonefile
|
29
|
-
@zonefile ||=
|
30
|
-
body = read
|
31
|
-
if body !~ /\sSOA\s/ # insert dummy SOA to trick parser if needed
|
32
|
-
body.sub!(/\n([^$])/, "\n@ 1 SOA example.com example.com ( 2000010101 1 1 1 1 )\n\\1")
|
33
|
-
end
|
34
|
-
Zonefile.load(body)
|
35
|
-
end
|
60
|
+
@zonefile ||= T.let(Zonefile.load(read), T.nilable(Zonefile))
|
36
61
|
end
|
37
62
|
|
38
|
-
|
39
|
-
|
63
|
+
sig { returns(String) }
|
64
|
+
def read
|
65
|
+
Kernel.raise NotImplementedError
|
40
66
|
end
|
41
67
|
|
42
|
-
|
43
|
-
|
68
|
+
sig { params(string: String).void }
|
69
|
+
def write string
|
70
|
+
Kernel.raise NotImplementedError
|
44
71
|
end
|
45
72
|
|
73
|
+
sig { params(record: Record).void }
|
46
74
|
def remove record
|
47
|
-
raise NotImplementedError
|
75
|
+
Kernel.raise NotImplementedError
|
48
76
|
end
|
49
77
|
|
78
|
+
sig { params(old_record: Record, new_record: Record).void }
|
50
79
|
def change old_record, new_record
|
51
|
-
raise NotImplementedError
|
80
|
+
Kernel.raise NotImplementedError
|
52
81
|
end
|
53
82
|
|
83
|
+
sig { params(record: Record).void }
|
54
84
|
def add record
|
55
|
-
raise NotImplementedError
|
85
|
+
Kernel.raise NotImplementedError
|
86
|
+
end
|
87
|
+
|
88
|
+
# Helper method for graceful duplicate record handling
|
89
|
+
# Child classes can use this in their add method implementations
|
90
|
+
sig { params(record: Record, block: T.proc.void).void }
|
91
|
+
def add_with_duplicate_handling record, &block
|
92
|
+
begin
|
93
|
+
block.call
|
94
|
+
rescue DuplicateRecordError => e
|
95
|
+
# Gracefully handle duplicate records - this means the record
|
96
|
+
# already exists and we just want to start tracking it
|
97
|
+
puts "Record already exists in #{self.class.name}: #{e.record.name} #{e.record.type} - will start tracking it"
|
98
|
+
return
|
99
|
+
end
|
56
100
|
end
|
57
101
|
end
|
58
102
|
|
@@ -60,22 +104,32 @@ module Zonesync
|
|
60
104
|
require "zonesync/route53"
|
61
105
|
|
62
106
|
class Memory < Provider
|
107
|
+
extend T::Sig
|
108
|
+
|
109
|
+
sig { returns(String) }
|
63
110
|
def read
|
64
|
-
|
111
|
+
config.fetch(:string)
|
65
112
|
end
|
66
113
|
|
114
|
+
sig { params(string: String).void }
|
67
115
|
def write string
|
68
|
-
|
116
|
+
config[:string] = string
|
117
|
+
nil
|
69
118
|
end
|
70
119
|
end
|
71
120
|
|
72
121
|
class Filesystem < Provider
|
122
|
+
extend T::Sig
|
123
|
+
|
124
|
+
sig { returns(String) }
|
73
125
|
def read
|
74
|
-
File.read(
|
126
|
+
File.read(config.fetch(:path))
|
75
127
|
end
|
76
128
|
|
129
|
+
sig { params(string: String).void }
|
77
130
|
def write string
|
78
|
-
File.write(
|
131
|
+
File.write(config.fetch(:path), string)
|
132
|
+
nil
|
79
133
|
end
|
80
134
|
end
|
81
135
|
end
|
data/lib/zonesync/record.rb
CHANGED
@@ -1,32 +1,22 @@
|
|
1
|
+
# typed: strict
|
2
|
+
require "sorbet-runtime"
|
3
|
+
|
1
4
|
module Zonesync
|
2
|
-
|
3
|
-
|
4
|
-
type = record.class.name.split("::").last
|
5
|
-
rdata = case type
|
6
|
-
when "SOA"
|
7
|
-
def record.host = origin
|
8
|
-
"" # it just gets ignored anyways
|
9
|
-
when "A", "AAAA"
|
10
|
-
record.address
|
11
|
-
when "CNAME", "NS", "PTR"
|
12
|
-
record.domainname
|
13
|
-
when "MX"
|
14
|
-
"#{record.priority} #{record.domainname}"
|
15
|
-
when "TXT", "SPF", "NAPTR"
|
16
|
-
record.data
|
17
|
-
else
|
18
|
-
raise NotImplementedError.new(record.class).to_s
|
19
|
-
end
|
5
|
+
Record = Struct.new(:name, :type, :ttl, :rdata, :comment, keyword_init: true) do
|
6
|
+
extend T::Sig
|
20
7
|
|
8
|
+
sig { params(record: Zonesync::Parser::Record).returns(Record) }
|
9
|
+
def self.from_dns_zonefile_record record
|
21
10
|
new(
|
22
11
|
name: record.host,
|
23
|
-
type
|
12
|
+
type: record.type,
|
24
13
|
ttl: record.ttl,
|
25
|
-
rdata
|
14
|
+
rdata: record.rdata,
|
26
15
|
comment: record.comment,
|
27
16
|
)
|
28
17
|
end
|
29
18
|
|
19
|
+
sig { params(origin: String).returns(String) }
|
30
20
|
def short_name origin
|
31
21
|
ret = name.sub(origin, "")
|
32
22
|
ret = ret.sub(/\.$/, "")
|
@@ -34,25 +24,30 @@ module Zonesync
|
|
34
24
|
ret
|
35
25
|
end
|
36
26
|
|
27
|
+
sig { returns(T::Boolean) }
|
37
28
|
def manifest?
|
38
29
|
type == "TXT" &&
|
39
|
-
name
|
30
|
+
name.match?(/^zonesync_manifest\./)
|
40
31
|
end
|
41
32
|
|
33
|
+
sig { returns(T::Boolean) }
|
42
34
|
def checksum?
|
43
35
|
type == "TXT" &&
|
44
|
-
name
|
36
|
+
name.match?(/^zonesync_checksum\./)
|
45
37
|
end
|
46
38
|
|
39
|
+
sig { params(other: Record).returns(Integer) }
|
47
40
|
def <=> other
|
48
41
|
to_sortable <=> other.to_sortable
|
49
42
|
end
|
50
43
|
|
44
|
+
sig { returns([Integer, String, String, String, Integer]) }
|
51
45
|
def to_sortable
|
52
46
|
is_soa = type == "SOA" ? 0 : 1
|
53
|
-
[is_soa, type, name, rdata, ttl]
|
47
|
+
[is_soa, type, name, rdata, ttl.to_i]
|
54
48
|
end
|
55
49
|
|
50
|
+
sig { returns(String) }
|
56
51
|
def to_s
|
57
52
|
string = [name, ttl, type, rdata].join(" ")
|
58
53
|
string << " ; #{comment}" if comment
|