record_store 5.5.3 → 5.5.4
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/.gitignore +1 -0
- data/.rubocop-https---shopify-github-io-ruby-style-guide-rubocop-yml +1027 -0
- data/.rubocop.yml +19 -0
- data/.travis.yml +1 -0
- data/bin/console +2 -2
- data/bin/record-store +1 -1
- data/bin/rubocop +29 -0
- data/dev.yml +4 -0
- data/lib/record_store.rb +4 -1
- data/lib/record_store/changeset.rb +8 -4
- data/lib/record_store/cli.rb +50 -48
- data/lib/record_store/provider.rb +11 -13
- data/lib/record_store/provider/dnsimple.rb +1 -2
- data/lib/record_store/provider/dynect.rb +6 -6
- data/lib/record_store/provider/google_cloud_dns.rb +5 -6
- data/lib/record_store/provider/ns1.rb +13 -9
- data/lib/record_store/provider/ns1/client.rb +3 -4
- data/lib/record_store/record.rb +27 -9
- data/lib/record_store/record/a.rb +4 -6
- data/lib/record_store/record/aaaa.rb +4 -6
- data/lib/record_store/record/alias.rb +5 -1
- data/lib/record_store/record/caa.rb +7 -3
- data/lib/record_store/record/cname.rb +5 -1
- data/lib/record_store/record/mx.rb +11 -3
- data/lib/record_store/record/ns.rb +5 -1
- data/lib/record_store/record/srv.rb +20 -4
- data/lib/record_store/record/txt.rb +1 -1
- data/lib/record_store/version.rb +1 -1
- data/lib/record_store/zone.rb +22 -16
- data/lib/record_store/zone/config.rb +1 -1
- data/lib/record_store/zone/yaml_definitions.rb +3 -2
- data/record_store.gemspec +4 -3
- data/template/bin/record-store +1 -1
- metadata +19 -2
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
inherit_from:
|
|
2
|
+
- https://shopify.github.io/ruby-style-guide/rubocop.yml
|
|
3
|
+
|
|
4
|
+
AllCops:
|
|
5
|
+
TargetRubyVersion: 2.4
|
|
6
|
+
Exclude:
|
|
7
|
+
- vendor/**/*
|
|
8
|
+
UseCache: true
|
|
9
|
+
CacheRootDirectory: tmp/rubocop
|
|
10
|
+
|
|
11
|
+
Style/FrozenStringLiteralComment:
|
|
12
|
+
Enabled: false
|
|
13
|
+
|
|
14
|
+
Style/MethodCallWithArgsParentheses:
|
|
15
|
+
Include:
|
|
16
|
+
- '**/*.rb'
|
|
17
|
+
|
|
18
|
+
Style/ClassAndModuleChildren:
|
|
19
|
+
Enabled: false
|
data/.travis.yml
CHANGED
data/bin/console
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
|
4
4
|
require 'bundler/setup'
|
|
5
5
|
require 'record_store'
|
|
6
6
|
|
|
@@ -8,4 +8,4 @@ RecordStore.zones_path = File.expand_path('../../dev/zones', __FILE__)
|
|
|
8
8
|
RecordStore.config_path = File.expand_path('../../dev/config.yml', __FILE__)
|
|
9
9
|
|
|
10
10
|
require 'pry'
|
|
11
|
-
binding.pry(RecordStore)
|
|
11
|
+
binding.pry(RecordStore) # rubocop:disable Lint/Debugger
|
data/bin/record-store
CHANGED
data/bin/rubocop
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
#
|
|
5
|
+
# This file was generated by Bundler.
|
|
6
|
+
#
|
|
7
|
+
# The application 'rubocop' is installed as part of a gem, and
|
|
8
|
+
# this file is here to facilitate running it.
|
|
9
|
+
#
|
|
10
|
+
|
|
11
|
+
require "pathname"
|
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
|
13
|
+
Pathname.new(__FILE__).realpath)
|
|
14
|
+
|
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
|
16
|
+
|
|
17
|
+
if File.file?(bundle_binstub)
|
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
|
19
|
+
load(bundle_binstub)
|
|
20
|
+
else
|
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
require "rubygems"
|
|
27
|
+
require "bundler/setup"
|
|
28
|
+
|
|
29
|
+
load Gem.bin_path("rubocop", "rubocop")
|
data/dev.yml
CHANGED
data/lib/record_store.rb
CHANGED
|
@@ -46,7 +46,10 @@ module RecordStore
|
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
def zones_path
|
|
49
|
-
@zones_path ||= Pathname.new(
|
|
49
|
+
@zones_path ||= Pathname.new(
|
|
50
|
+
File.expand_path(config.fetch('zones_path'),
|
|
51
|
+
File.dirname(config_path)),
|
|
52
|
+
).realpath.to_s
|
|
50
53
|
end
|
|
51
54
|
|
|
52
55
|
def config_path
|
|
@@ -4,7 +4,9 @@ module RecordStore
|
|
|
4
4
|
attr_accessor :type, :record, :id
|
|
5
5
|
|
|
6
6
|
def initialize(type: nil, record: nil, id: nil)
|
|
7
|
-
@type
|
|
7
|
+
@type = type
|
|
8
|
+
@record = record
|
|
9
|
+
@id = id
|
|
8
10
|
end
|
|
9
11
|
|
|
10
12
|
def self.addition(record)
|
|
@@ -37,7 +39,7 @@ module RecordStore
|
|
|
37
39
|
def self.build_from(provider:, zone:)
|
|
38
40
|
current_zone = provider.build_zone(zone_name: zone.unrooted_name, config: zone.config)
|
|
39
41
|
|
|
40
|
-
|
|
42
|
+
new(
|
|
41
43
|
current_records: current_zone.records,
|
|
42
44
|
desired_records: zone.records,
|
|
43
45
|
provider: provider,
|
|
@@ -51,7 +53,9 @@ module RecordStore
|
|
|
51
53
|
@provider = provider
|
|
52
54
|
@zone = zone
|
|
53
55
|
|
|
54
|
-
@additions
|
|
56
|
+
@additions = []
|
|
57
|
+
@removals = []
|
|
58
|
+
@updates = []
|
|
55
59
|
|
|
56
60
|
build_changeset
|
|
57
61
|
end
|
|
@@ -81,7 +85,7 @@ module RecordStore
|
|
|
81
85
|
def build_changeset
|
|
82
86
|
current_records_set = (current_records - unchanged).sort_by(&:to_s).group_by(&:key)
|
|
83
87
|
desired_records_set = (desired_records - unchanged).sort_by(&:to_s).group_by(&:key)
|
|
84
|
-
current_records_set.default_proc = desired_records_set.default_proc =
|
|
88
|
+
current_records_set.default_proc = desired_records_set.default_proc = proc { [] }
|
|
85
89
|
|
|
86
90
|
record_keys = current_records_set.keys | desired_records_set.keys
|
|
87
91
|
|
data/lib/record_store/cli.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require 'English'
|
|
2
|
+
|
|
1
3
|
module RecordStore
|
|
2
4
|
class CLI < Thor
|
|
3
5
|
class_option :config, desc: 'Path to config.yml', aliases: '-c'
|
|
@@ -34,9 +36,7 @@ module RecordStore
|
|
|
34
36
|
def list
|
|
35
37
|
Zone.each do |name, zone|
|
|
36
38
|
puts "Zone: #{name}"
|
|
37
|
-
zone.records.each
|
|
38
|
-
record.log!
|
|
39
|
-
end
|
|
39
|
+
zone.records.each(&:log!)
|
|
40
40
|
end
|
|
41
41
|
end
|
|
42
42
|
|
|
@@ -60,34 +60,33 @@ module RecordStore
|
|
|
60
60
|
next if !options.fetch('verbose') && changeset.changes.empty?
|
|
61
61
|
|
|
62
62
|
puts '-' * 20
|
|
63
|
-
puts "Provider: #{changeset.provider
|
|
63
|
+
puts "Provider: #{changeset.provider}"
|
|
64
64
|
|
|
65
65
|
if !changeset.additions.empty? || options.fetch('verbose')
|
|
66
66
|
puts "Add:"
|
|
67
67
|
changeset.additions.map(&:record).each do |record|
|
|
68
|
-
puts " - #{record
|
|
68
|
+
puts " - #{record}"
|
|
69
69
|
end
|
|
70
70
|
end
|
|
71
71
|
|
|
72
72
|
if !changeset.removals.empty? || options.fetch('verbose')
|
|
73
73
|
puts "Remove:"
|
|
74
74
|
changeset.removals.map(&:record).each do |record|
|
|
75
|
-
puts " - #{record
|
|
75
|
+
puts " - #{record}"
|
|
76
76
|
end
|
|
77
77
|
end
|
|
78
78
|
|
|
79
79
|
if !changeset.updates.empty? || options.fetch('verbose')
|
|
80
80
|
puts "Update:"
|
|
81
81
|
changeset.updates.map(&:record).each do |record|
|
|
82
|
-
puts " - #{record
|
|
82
|
+
puts " - #{record}"
|
|
83
83
|
end
|
|
84
84
|
end
|
|
85
85
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
end
|
|
86
|
+
next unless options.fetch('verbose')
|
|
87
|
+
puts "Unchanged:"
|
|
88
|
+
changeset.unchanged.each do |record|
|
|
89
|
+
puts " - #{record}"
|
|
91
90
|
end
|
|
92
91
|
end
|
|
93
92
|
puts '=' * 20
|
|
@@ -105,7 +104,7 @@ module RecordStore
|
|
|
105
104
|
end
|
|
106
105
|
|
|
107
106
|
zones.each do |zone|
|
|
108
|
-
abort
|
|
107
|
+
abort("Attempted to apply invalid zone: #{zone.name}") unless zone.valid?
|
|
109
108
|
|
|
110
109
|
changesets = zone.build_changesets
|
|
111
110
|
changesets.each(&:apply)
|
|
@@ -116,11 +115,12 @@ module RecordStore
|
|
|
116
115
|
|
|
117
116
|
option :name, desc: 'Zone to download', aliases: '-n', type: :string, required: true
|
|
118
117
|
option :provider, desc: 'Provider in which this zone exists', aliases: '-p', type: :string
|
|
119
|
-
desc 'download', 'Downloads all records from zone and creates YAML zone definition in zones/
|
|
118
|
+
desc 'download', 'Downloads all records from zone and creates YAML zone definition in zones/ '\
|
|
119
|
+
'e.g. record-store download --name=shopify.io'
|
|
120
120
|
def download
|
|
121
121
|
name = options.fetch('name')
|
|
122
|
-
abort
|
|
123
|
-
abort
|
|
122
|
+
abort('Please omit the period at the end of the zone') if name.ends_with?('.')
|
|
123
|
+
abort('Zone with this name already exists in zones/') if File.exist?("#{RecordStore.zones_path}/#{name}.yml")
|
|
124
124
|
|
|
125
125
|
provider = options.fetch('provider', Provider.provider_for(name))
|
|
126
126
|
if provider.nil?
|
|
@@ -151,11 +151,12 @@ module RecordStore
|
|
|
151
151
|
desc 'sort', 'Sorts the zonefile alphabetically e.g. record-store sort --name=shopify.io'
|
|
152
152
|
def sort
|
|
153
153
|
name = options.fetch('name')
|
|
154
|
-
abort
|
|
154
|
+
abort("Please omit the period at the end of the zone") if name.ends_with?('.')
|
|
155
155
|
|
|
156
156
|
yaml = YAML.load_file("#{RecordStore.zones_path}/#{name}.yml")
|
|
157
|
-
yaml.fetch(name).fetch('records').sort_by!
|
|
158
|
-
|
|
157
|
+
yaml.fetch(name).fetch('records').sort_by! do |r|
|
|
158
|
+
[r.fetch('fqdn'), r.fetch('type'), r['nsdname'] || r['address']]
|
|
159
|
+
end
|
|
159
160
|
File.write("#{RecordStore.zones_path}/#{name}.yml", yaml.deep_stringify_keys.to_yaml.gsub("---\n", ''))
|
|
160
161
|
end
|
|
161
162
|
|
|
@@ -171,11 +172,11 @@ module RecordStore
|
|
|
171
172
|
end
|
|
172
173
|
end
|
|
173
174
|
|
|
174
|
-
secrets =
|
|
175
|
-
if
|
|
175
|
+
secrets = %x(ejson decrypt #{RecordStore.secrets_path.sub(/\.json\z/, ".#{environment}.ejson")})
|
|
176
|
+
if $CHILD_STATUS.success?
|
|
176
177
|
File.write(RecordStore.secrets_path, secrets)
|
|
177
178
|
else
|
|
178
|
-
abort
|
|
179
|
+
abort(secrets)
|
|
179
180
|
end
|
|
180
181
|
end
|
|
181
182
|
|
|
@@ -184,7 +185,7 @@ module RecordStore
|
|
|
184
185
|
zones = Zone.modified.map(&:name)
|
|
185
186
|
|
|
186
187
|
unless zones.empty?
|
|
187
|
-
abort
|
|
188
|
+
abort("The following zones have diverged: #{zones.join(', ')}")
|
|
188
189
|
end
|
|
189
190
|
end
|
|
190
191
|
|
|
@@ -200,18 +201,18 @@ module RecordStore
|
|
|
200
201
|
end
|
|
201
202
|
|
|
202
203
|
invalid_records = zone.records.reject(&:valid?)
|
|
203
|
-
puts ' Invalid records'
|
|
204
|
+
puts ' Invalid records' unless invalid_records.empty?
|
|
204
205
|
|
|
205
206
|
invalid_records.each do |record|
|
|
206
|
-
puts " #{record
|
|
207
|
+
puts " #{record}"
|
|
207
208
|
record.errors.each do |field, msg|
|
|
208
209
|
puts " - #{field}: #{msg}"
|
|
209
210
|
end
|
|
210
211
|
end
|
|
211
212
|
end
|
|
212
213
|
|
|
213
|
-
if invalid_zones.
|
|
214
|
-
abort
|
|
214
|
+
if invalid_zones.present?
|
|
215
|
+
abort("The following zones were invalid: #{invalid_zones.join(', ')}")
|
|
215
216
|
else
|
|
216
217
|
puts "All zones have valid definitions."
|
|
217
218
|
end
|
|
@@ -227,7 +228,10 @@ module RecordStore
|
|
|
227
228
|
end
|
|
228
229
|
|
|
229
230
|
unless removals.empty?
|
|
230
|
-
|
|
231
|
+
error = +"As a safety measure, you cannot remove more than #{MAXIMUM_REMOVALS} "
|
|
232
|
+
error << 'records at a time per zone. '
|
|
233
|
+
error << "(zones failing this: #{removals.map(&:name).join(', ')})"
|
|
234
|
+
abort(error)
|
|
231
235
|
end
|
|
232
236
|
end
|
|
233
237
|
end
|
|
@@ -235,30 +239,28 @@ module RecordStore
|
|
|
235
239
|
SKIP_CHECKS = 'SKIP_DEPLOY_VALIDATIONS'
|
|
236
240
|
desc 'validate_initial_state', "Validates state hasn't diverged since the last deploy"
|
|
237
241
|
def validate_initial_state
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
abort "Checkout of old commit failed" if $?.exitstatus != 0
|
|
242
|
+
assert_empty_diff
|
|
243
|
+
puts "Deploy will cause no changes, no need to validate initial state"
|
|
244
|
+
rescue SystemExit
|
|
245
|
+
if File.exist?(File.expand_path(SKIP_CHECKS, Dir.pwd))
|
|
246
|
+
puts "Found '#{SKIP_CHECKS}', skipping predeploy validations"
|
|
247
|
+
else
|
|
248
|
+
puts "Checkout git SHA #{ENV['LAST_DEPLOYED_SHA']}"
|
|
249
|
+
%x(git checkout #{ENV['LAST_DEPLOYED_SHA']})
|
|
250
|
+
abort("Checkout of old commit failed") unless $CHILD_STATUS.success?
|
|
248
251
|
|
|
249
|
-
|
|
250
|
-
|
|
252
|
+
%x(record-store secrets)
|
|
253
|
+
abort("Decrypt secrets failed") unless $CHILD_STATUS.success?
|
|
251
254
|
|
|
252
|
-
|
|
253
|
-
|
|
255
|
+
%x(record-store assert_empty_diff)
|
|
256
|
+
abort("Dyn status has diverged!") unless $CHILD_STATUS.success?
|
|
254
257
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
+
puts "Checkout git SHA #{ENV['REVISION']}"
|
|
259
|
+
%x(git checkout #{ENV['REVISION']})
|
|
260
|
+
abort("Checkout of new commit failed") unless $CHILD_STATUS.success?
|
|
258
261
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
end
|
|
262
|
+
%x(record-store secrets)
|
|
263
|
+
abort("Decrypt secrets failed") unless $CHILD_STATUS.success?
|
|
262
264
|
end
|
|
263
265
|
end
|
|
264
266
|
|
|
@@ -9,20 +9,18 @@ module RecordStore
|
|
|
9
9
|
begin
|
|
10
10
|
ns_server = dns.getresource(zone_name, Resolv::DNS::Resource::IN::SOA).mname.to_s
|
|
11
11
|
rescue Resolv::ResolvError
|
|
12
|
-
abort
|
|
12
|
+
abort("Domain doesn't exist")
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
case ns_server
|
|
16
|
-
when
|
|
16
|
+
when /\.dnsimple\.com\z/
|
|
17
17
|
'DNSimple'
|
|
18
|
-
when
|
|
18
|
+
when /\.dynect\.net\z/
|
|
19
19
|
'DynECT'
|
|
20
|
-
when
|
|
20
|
+
when /\.googledomains\.com\z/
|
|
21
21
|
'GoogleCloudDNS'
|
|
22
22
|
when /\.nsone\.net\z/
|
|
23
23
|
'NS1'
|
|
24
|
-
else
|
|
25
|
-
nil
|
|
26
24
|
end
|
|
27
25
|
end
|
|
28
26
|
|
|
@@ -54,7 +52,7 @@ module RecordStore
|
|
|
54
52
|
end
|
|
55
53
|
|
|
56
54
|
# returns an array of Record objects that match the records which exist in the provider
|
|
57
|
-
def retrieve_current_records(zone:, stdout: $stdout)
|
|
55
|
+
def retrieve_current_records(zone:, stdout: $stdout) # rubocop:disable Lint/UnusedMethodArgument
|
|
58
56
|
raise NotImplementedError
|
|
59
57
|
end
|
|
60
58
|
|
|
@@ -91,28 +89,28 @@ module RecordStore
|
|
|
91
89
|
end
|
|
92
90
|
|
|
93
91
|
def thawable?
|
|
94
|
-
|
|
92
|
+
respond_to?(:thaw_zone)
|
|
95
93
|
end
|
|
96
94
|
|
|
97
95
|
def freezable?
|
|
98
|
-
|
|
96
|
+
respond_to?(:freeze_zone)
|
|
99
97
|
end
|
|
100
98
|
|
|
101
99
|
def to_s
|
|
102
|
-
|
|
100
|
+
name.demodulize
|
|
103
101
|
end
|
|
104
102
|
|
|
105
103
|
private
|
|
106
104
|
|
|
107
|
-
def add(record)
|
|
105
|
+
def add(record) # rubocop:disable Lint/UnusedMethodArgument
|
|
108
106
|
raise NotImplementedError
|
|
109
107
|
end
|
|
110
108
|
|
|
111
|
-
def remove(record)
|
|
109
|
+
def remove(record) # rubocop:disable Lint/UnusedMethodArgument
|
|
112
110
|
raise NotImplementedError
|
|
113
111
|
end
|
|
114
112
|
|
|
115
|
-
def update(id, record)
|
|
113
|
+
def update(id, record) # rubocop:disable Lint/UnusedMethodArgument
|
|
116
114
|
raise NotImplementedError
|
|
117
115
|
end
|
|
118
116
|
end
|
|
@@ -113,7 +113,7 @@ module RecordStore
|
|
|
113
113
|
|
|
114
114
|
def api_hash(record, zone)
|
|
115
115
|
record_hash = {
|
|
116
|
-
name: record.fqdn.gsub(
|
|
116
|
+
name: record.fqdn.gsub(Record.ensure_ends_with_dot(zone).to_s, '').chomp('.'),
|
|
117
117
|
ttl: record.ttl,
|
|
118
118
|
type: record.type,
|
|
119
119
|
}
|
|
@@ -138,7 +138,6 @@ module RecordStore
|
|
|
138
138
|
record_hash[:content] = "#{record.weight} #{record.port} #{record.target.chomp('.')}"
|
|
139
139
|
record_hash[:priority] = record.priority
|
|
140
140
|
end
|
|
141
|
-
|
|
142
141
|
record_hash
|
|
143
142
|
end
|
|
144
143
|
end
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
require 'fog/dynect'
|
|
2
2
|
require 'limiter'
|
|
3
3
|
|
|
4
|
-
Fog::DNS::Dynect::Real.extend
|
|
5
|
-
Fog::DNS::Dynect::Real.limit_method
|
|
4
|
+
Fog::DNS::Dynect::Real.extend(Limiter::Mixin)
|
|
5
|
+
Fog::DNS::Dynect::Real.limit_method(:request, rate: 5, interval: 1) # 5 RPS == 300 RPM
|
|
6
6
|
|
|
7
7
|
module RecordStore
|
|
8
8
|
class Provider::DynECT < Provider
|
|
@@ -34,7 +34,7 @@ module RecordStore
|
|
|
34
34
|
|
|
35
35
|
# returns an array of Record objects that match the records which exist in the provider
|
|
36
36
|
def retrieve_current_records(zone:, stdout: $stdout)
|
|
37
|
-
session.get_all_records(zone).body.fetch('data').flat_map do |
|
|
37
|
+
session.get_all_records(zone).body.fetch('data').flat_map do |_type, records|
|
|
38
38
|
records.map do |record_body|
|
|
39
39
|
begin
|
|
40
40
|
build_from_api(record_body)
|
|
@@ -88,8 +88,8 @@ module RecordStore
|
|
|
88
88
|
|
|
89
89
|
def api_rdata(record)
|
|
90
90
|
case record.type
|
|
91
|
-
when 'TXT'
|
|
92
|
-
{ txtdata: record.rdata_txt }
|
|
91
|
+
when 'SPF', 'TXT'
|
|
92
|
+
{ txtdata: Record.long_quote(record.rdata_txt) }
|
|
93
93
|
else
|
|
94
94
|
record.rdata
|
|
95
95
|
end
|
|
@@ -102,7 +102,7 @@ module RecordStore
|
|
|
102
102
|
type = record.fetch(:record_type)
|
|
103
103
|
return if type == 'SOA'
|
|
104
104
|
|
|
105
|
-
record[:txtdata] = Record.unescape(record[:txtdata]) if %w[SPF TXT].include?(type)
|
|
105
|
+
record[:txtdata] = Record.unlong_quote(Record.unescape(record[:txtdata])) if %w[SPF TXT].include?(type)
|
|
106
106
|
|
|
107
107
|
fqdn = record.fetch(:fqdn)
|
|
108
108
|
fqdn = "#{fqdn}." unless fqdn.ends_with?('.')
|