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.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -0
  3. data/lib/zonesync/cli.rb +9 -0
  4. data/lib/zonesync/cloudflare.rb +64 -39
  5. data/lib/zonesync/diff.rb +10 -1
  6. data/lib/zonesync/errors.rb +35 -0
  7. data/lib/zonesync/generate.rb +14 -0
  8. data/lib/zonesync/http.rb +24 -8
  9. data/lib/zonesync/logger.rb +20 -15
  10. data/lib/zonesync/manifest.rb +21 -5
  11. data/lib/zonesync/parser.rb +337 -0
  12. data/lib/zonesync/provider.rb +80 -26
  13. data/lib/zonesync/record.rb +18 -23
  14. data/lib/zonesync/route53.rb +48 -27
  15. data/lib/zonesync/sync.rb +40 -0
  16. data/lib/zonesync/validator.rb +19 -15
  17. data/lib/zonesync/version.rb +1 -1
  18. data/lib/zonesync/zonefile.rb +22 -311
  19. data/lib/zonesync.rb +27 -60
  20. data/sorbet/config +4 -0
  21. data/sorbet/rbi/annotations/.gitattributes +1 -0
  22. data/sorbet/rbi/annotations/activesupport.rbi +457 -0
  23. data/sorbet/rbi/annotations/minitest.rbi +119 -0
  24. data/sorbet/rbi/annotations/webmock.rbi +9 -0
  25. data/sorbet/rbi/gems/.gitattributes +1 -0
  26. data/sorbet/rbi/gems/activesupport@8.0.1.rbi +18474 -0
  27. data/sorbet/rbi/gems/addressable@2.8.7.rbi +1994 -0
  28. data/sorbet/rbi/gems/base64@0.2.0.rbi +507 -0
  29. data/sorbet/rbi/gems/benchmark@0.4.0.rbi +618 -0
  30. data/sorbet/rbi/gems/bigdecimal@3.1.9.rbi +9 -0
  31. data/sorbet/rbi/gems/concurrent-ruby@1.3.4.rbi +11645 -0
  32. data/sorbet/rbi/gems/connection_pool@2.4.1.rbi +9 -0
  33. data/sorbet/rbi/gems/crack@1.0.0.rbi +145 -0
  34. data/sorbet/rbi/gems/date@3.4.1.rbi +75 -0
  35. data/sorbet/rbi/gems/diff-lcs@1.5.1.rbi +1131 -0
  36. data/sorbet/rbi/gems/drb@2.2.1.rbi +1347 -0
  37. data/sorbet/rbi/gems/erubi@1.13.1.rbi +155 -0
  38. data/sorbet/rbi/gems/hashdiff@1.1.2.rbi +353 -0
  39. data/sorbet/rbi/gems/i18n@1.14.6.rbi +2275 -0
  40. data/sorbet/rbi/gems/io-console@0.8.0.rbi +9 -0
  41. data/sorbet/rbi/gems/logger@1.6.4.rbi +940 -0
  42. data/sorbet/rbi/gems/minitest@5.25.4.rbi +1547 -0
  43. data/sorbet/rbi/gems/netrc@0.11.0.rbi +159 -0
  44. data/sorbet/rbi/gems/parallel@1.26.3.rbi +291 -0
  45. data/sorbet/rbi/gems/polyglot@0.3.5.rbi +42 -0
  46. data/sorbet/rbi/gems/prism@1.3.0.rbi +40040 -0
  47. data/sorbet/rbi/gems/psych@5.2.2.rbi +1785 -0
  48. data/sorbet/rbi/gems/public_suffix@6.0.1.rbi +936 -0
  49. data/sorbet/rbi/gems/rake@13.2.1.rbi +3028 -0
  50. data/sorbet/rbi/gems/rbi@0.2.2.rbi +4527 -0
  51. data/sorbet/rbi/gems/rdoc@6.10.0.rbi +12766 -0
  52. data/sorbet/rbi/gems/reline@0.6.0.rbi +9 -0
  53. data/sorbet/rbi/gems/rexml@3.4.0.rbi +4974 -0
  54. data/sorbet/rbi/gems/rspec-core@3.13.2.rbi +10896 -0
  55. data/sorbet/rbi/gems/rspec-expectations@3.13.3.rbi +8183 -0
  56. data/sorbet/rbi/gems/rspec-mocks@3.13.2.rbi +5341 -0
  57. data/sorbet/rbi/gems/rspec-support@3.13.2.rbi +1630 -0
  58. data/sorbet/rbi/gems/rspec@3.13.0.rbi +83 -0
  59. data/sorbet/rbi/gems/securerandom@0.4.1.rbi +75 -0
  60. data/sorbet/rbi/gems/spoom@1.5.0.rbi +4932 -0
  61. data/sorbet/rbi/gems/stringio@3.1.2.rbi +9 -0
  62. data/sorbet/rbi/gems/tapioca@0.16.6.rbi +3611 -0
  63. data/sorbet/rbi/gems/thor@1.3.2.rbi +4378 -0
  64. data/sorbet/rbi/gems/treetop@1.6.12.rbi +1895 -0
  65. data/sorbet/rbi/gems/tzinfo@2.0.6.rbi +5918 -0
  66. data/sorbet/rbi/gems/uri@1.0.2.rbi +2340 -0
  67. data/sorbet/rbi/gems/webmock@3.24.0.rbi +1780 -0
  68. data/sorbet/rbi/gems/yard-sorbet@0.9.0.rbi +435 -0
  69. data/sorbet/rbi/gems/yard@0.9.37.rbi +18379 -0
  70. data/sorbet/rbi/todo.rbi +7 -0
  71. data/sorbet/tapioca/config.yml +13 -0
  72. data/sorbet/tapioca/require.rb +4 -0
  73. data/zonesync.gemspec +3 -0
  74. 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
+
@@ -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 < Struct.new(:credentials)
7
- def self.from credentials
8
- return credentials if credentials.is_a?(Provider)
9
- Zonesync.const_get(credentials[:provider]).new(credentials)
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.map do |record|
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
- @manifest ||= Manifest.new(records, zonefile)
55
+ Manifest.new(records, zonefile)
26
56
  end
27
57
 
58
+ sig { returns(Zonefile) }
28
59
  private def zonefile
29
- @zonefile ||= begin
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
- def read record
39
- raise NotImplementedError
63
+ sig { returns(String) }
64
+ def read
65
+ Kernel.raise NotImplementedError
40
66
  end
41
67
 
42
- def write text
43
- raise NotImplementedError
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
- credentials[:string]
111
+ config.fetch(:string)
65
112
  end
66
113
 
114
+ sig { params(string: String).void }
67
115
  def write string
68
- credentials[:string] = string
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(credentials[:path])
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(credentials[:path], string)
131
+ File.write(config.fetch(:path), string)
132
+ nil
79
133
  end
80
134
  end
81
135
  end
@@ -1,32 +1,22 @@
1
+ # typed: strict
2
+ require "sorbet-runtime"
3
+
1
4
  module Zonesync
2
- class Record < Struct.new(:name, :type, :ttl, :rdata, :comment, keyword_init: true)
3
- def self.from_dns_zonefile_record record
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 =~ /^zonesync_manifest\./
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 =~ /^zonesync_checksum\./
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