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.
@@ -3,7 +3,7 @@ require 'google/cloud/dns'
3
3
  module RecordStore
4
4
  class Provider::GoogleCloudDNS < Provider
5
5
  class << self
6
- def apply_changeset(changeset, stdout = $stdout)
6
+ def apply_changeset(changeset, _stdout = nil)
7
7
  zone = session.zone(convert_to_name(changeset.zone))
8
8
 
9
9
  deletions = convert_records_to_gcloud_record_sets(zone, changeset.current_records)
@@ -45,10 +45,10 @@ module RecordStore
45
45
 
46
46
  def session
47
47
  @dns ||= begin
48
- Google::Cloud::Dns.new({
48
+ Google::Cloud::Dns.new(
49
49
  project_id: secrets.fetch('project_id'),
50
50
  credentials: Google::Cloud::Dns::Credentials.new(secrets),
51
- })
51
+ )
52
52
  end
53
53
  end
54
54
 
@@ -72,9 +72,8 @@ module RecordStore
72
72
  [record.type, record.fqdn]
73
73
  end
74
74
 
75
-
76
75
  record_sets.map do |(rr_type, rr_fqdn), records_for_set|
77
- zone.record(rr_fqdn, rr_type, records_for_set[0].ttl, records_for_set.map(&:rdata_txt))
76
+ zone.record(rr_fqdn, rr_type, records_for_set[0].ttl, Record.long_quote(records_for_set.map(&:rdata_txt)))
78
77
  end
79
78
  end
80
79
 
@@ -109,7 +108,7 @@ module RecordStore
109
108
  when 'NS'
110
109
  record_params.merge!(nsdname: record.data[0])
111
110
  when 'SPF', 'TXT'
112
- txtdata = Record.unquote(record.data[0]).gsub(';', '\;')
111
+ txtdata = Record.unlong_quote(record.data[0]).gsub(';', '\;')
113
112
  record_params.merge!(txtdata: txtdata)
114
113
  when 'SRV'
115
114
  priority, weight, port, target = record.data[0].split(' ')
@@ -12,7 +12,7 @@ module RecordStore
12
12
  # Downloads all the records from the provider.
13
13
  #
14
14
  # Returns: an array of `Record` for each record in the provider's zone
15
- def retrieve_current_records(zone:, stdout: $stdout)
15
+ def retrieve_current_records(zone:, stdout: $stdout) # rubocop:disable Lint/UnusedMethodArgument
16
16
  full_api_records = records_for_zone(zone).map do |short_record|
17
17
  client.record(
18
18
  zone: zone,
@@ -22,7 +22,7 @@ module RecordStore
22
22
  )
23
23
  end
24
24
 
25
- full_api_records.map { |r| build_from_api(r, zone) }.flatten.compact
25
+ full_api_records.map { |r| build_from_api(r) }.flatten.compact
26
26
  end
27
27
 
28
28
  # Returns an array of the zones managed by provider as strings
@@ -129,7 +129,11 @@ module RecordStore
129
129
  answer["answer"] = build_api_answer_from_record(record)
130
130
  end
131
131
 
132
- raise(Error, "while trying to update a record, could not find answer with fqdn: #{record.fqdn}, type; #{record.type}, id: #{id}") unless updated
132
+ unless updated
133
+ error = +'while trying to update a record, could not find answer with fqdn: '
134
+ error << "#{record.fqdn}, type; #{record.type}, id: #{id}"
135
+ raise Error, error
136
+ end
133
137
 
134
138
  client.modify_record(
135
139
  zone: zone,
@@ -139,7 +143,7 @@ module RecordStore
139
143
  )
140
144
  end
141
145
 
142
- def build_from_api(api_record, zone)
146
+ def build_from_api(api_record)
143
147
  fqdn = Record.ensure_ends_with_dot(api_record["domain"])
144
148
 
145
149
  record_type = api_record["type"]
@@ -150,7 +154,7 @@ module RecordStore
150
154
  record = {
151
155
  ttl: api_record["ttl"],
152
156
  fqdn: fqdn.downcase,
153
- record_id: api_answer["id"]
157
+ record_id: api_answer["id"],
154
158
  }
155
159
 
156
160
  case record_type
@@ -179,7 +183,7 @@ module RecordStore
179
183
  when 'NS'
180
184
  record.merge!(nsdname: answer.first)
181
185
  when 'SPF', 'TXT'
182
- record.merge!(txtdata: Record.unescape(answer.first).gsub(';', '\;'))
186
+ record.merge!(txtdata: Record.unlong_quote(Record.unescape(answer.first).gsub(';', '\;')))
183
187
  when 'SRV'
184
188
  priority, weight, port, host = answer
185
189
 
@@ -197,8 +201,8 @@ module RecordStore
197
201
  def build_api_answer_from_record(record)
198
202
  if record.is_a?(Record::MX)
199
203
  [record.preference, record.exchange]
200
- elsif record.is_a?(Record::TXT) or record.is_a?(Record::SPF)
201
- [record.txtdata]
204
+ elsif record.is_a?(Record::TXT) || record.is_a?(Record::SPF)
205
+ [Record.long_quote(record.txtdata)]
202
206
  elsif record.is_a?(Record::CAA)
203
207
  [record.flags, record.tag, record.value]
204
208
  elsif record.is_a?(Record::SRV)
@@ -209,7 +213,7 @@ module RecordStore
209
213
  end
210
214
 
211
215
  def symbolize_keys(hash)
212
- hash.map{ |key, value| [key.to_sym, value] }.to_h
216
+ hash.map { |key, value| [key.to_sym, value] }.to_h
213
217
  end
214
218
 
215
219
  def secrets
@@ -26,22 +26,21 @@ module RecordStore
26
26
 
27
27
  def create_record(zone:, fqdn:, type:, params:)
28
28
  result = super(zone, fqdn, type, params)
29
- raise(Error, result.to_s) if result.is_a? NS1::Response::Error
29
+ raise(Error, result.to_s) if result.is_a?(NS1::Response::Error)
30
30
  nil
31
31
  end
32
32
 
33
33
  def modify_record(zone:, fqdn:, type:, params:)
34
34
  result = super(zone, fqdn, type, params)
35
- raise(Error, result.to_s) if result.is_a? NS1::Response::Error
35
+ raise(Error, result.to_s) if result.is_a?(NS1::Response::Error)
36
36
  nil
37
37
  end
38
38
 
39
39
  def delete_record(zone:, fqdn:, type:)
40
40
  result = super(zone, fqdn, type)
41
- raise(Error, result.to_s) if result.is_a? NS1::Response::Error
41
+ raise(Error, result.to_s) if result.is_a?(NS1::Response::Error)
42
42
  nil
43
43
  end
44
44
  end
45
45
  end
46
-
47
46
  end
@@ -1,7 +1,7 @@
1
1
  module RecordStore
2
2
  class Record
3
3
  FQDN_REGEX = /\A(\*\.)?([a-z0-9_]+(-[a-z0-9]+)*\._?)+[a-z]{2,}\.\Z/i
4
- CNAME_REGEX = /\A(\*\.)?([a-z0-9_]+((-|--)?[a-z0-9]+)*\._?)+[a-z]{2,}\.\Z/i
4
+ CNAME_REGEX = /\A(\*\.)?([a-z0-9_]+((-|--)?[a-z0-9]+)*\._?)+[a-z]{2,}\.\Z/i
5
5
 
6
6
  include ActiveModel::Validations
7
7
 
@@ -17,7 +17,21 @@ module RecordStore
17
17
  end
18
18
 
19
19
  def quote(value)
20
- %("#{escape(value)}")
20
+ result = escape(value)
21
+ %("#{result}")
22
+ end
23
+
24
+ def long_quote(value)
25
+ result = value
26
+ if needs_long_quotes?(value)
27
+ result = unquote(value).scan(/.{1,255}/).join('" "')
28
+ result = %("#{result}")
29
+ end
30
+ result
31
+ end
32
+
33
+ def unlong_quote(value)
34
+ value.length > 255 ? value.scan(/.{1,258}/).map { |x| x.sub(/^\"/, "").sub(/\" ?$/, "") }.join : unquote(value)
21
35
  end
22
36
 
23
37
  def unescape(value)
@@ -27,6 +41,14 @@ module RecordStore
27
41
  def unquote(value)
28
42
  unescape(value.sub(/\A"(.*)"\z/, '\1'))
29
43
  end
44
+
45
+ def ensure_ends_with_dot(fqdn)
46
+ fqdn.end_with?(".") ? fqdn : "#{fqdn}."
47
+ end
48
+
49
+ def needs_long_quotes?(value)
50
+ value.length > 255 && value !~ /^((\\)?"((\\"|[^"])){1,255}(\\)?"\s*)+$/
51
+ end
30
52
  end
31
53
 
32
54
  def initialize(record)
@@ -40,7 +62,7 @@ module RecordStore
40
62
  Record.const_get(record_type).new(yaml_definition)
41
63
  end
42
64
 
43
- def log!(logger=STDOUT)
65
+ def log!(logger = STDOUT)
44
66
  logger.puts to_s
45
67
  end
46
68
 
@@ -48,7 +70,7 @@ module RecordStore
48
70
  {
49
71
  type: type,
50
72
  fqdn: fqdn,
51
- ttl: ttl
73
+ ttl: ttl,
52
74
  }.merge(rdata)
53
75
  end
54
76
 
@@ -57,7 +79,7 @@ module RecordStore
57
79
  end
58
80
 
59
81
  def ==(other)
60
- other.class == self.class && other.to_hash == self.to_hash
82
+ other.class == self.class && other.to_hash == to_hash
61
83
  end
62
84
 
63
85
  alias_method :eql?, :==
@@ -93,9 +115,5 @@ module RecordStore
93
115
  errors.add(:fqdn, "A label should be at most 63 characters")
94
116
  end
95
117
  end
96
-
97
- def self.ensure_ends_with_dot(fqdn)
98
- fqdn.end_with?(".") ? fqdn : "#{fqdn}."
99
- end
100
118
  end
101
119
  end
@@ -21,12 +21,10 @@ module RecordStore
21
21
  private
22
22
 
23
23
  def valid_address?
24
- begin
25
- ip = IPAddr.new(address)
26
- errors.add(:address, 'is not an IPv4 address') unless ip.ipv4?
27
- rescue IPAddr::InvalidAddressError
28
- errors.add(:address, 'is invalid')
29
- end
24
+ ip = IPAddr.new(address)
25
+ errors.add(:address, 'is not an IPv4 address') unless ip.ipv4?
26
+ rescue IPAddr::InvalidAddressError
27
+ errors.add(:address, 'is invalid')
30
28
  end
31
29
  end
32
30
  end
@@ -21,12 +21,10 @@ module RecordStore
21
21
  private
22
22
 
23
23
  def valid_address?
24
- begin
25
- ip = IPAddr.new(address)
26
- errors.add(:address, 'is not an IPv6 address') unless ip.ipv6?
27
- rescue IPAddr::InvalidAddressError
28
- errors.add(:address, 'is invalid')
29
- end
24
+ ip = IPAddr.new(address)
25
+ errors.add(:address, 'is not an IPv6 address') unless ip.ipv6?
26
+ rescue IPAddr::InvalidAddressError
27
+ errors.add(:address, 'is invalid')
30
28
  end
31
29
  end
32
30
  end
@@ -2,7 +2,11 @@ module RecordStore
2
2
  class Record::ALIAS < Record
3
3
  attr_accessor :alias
4
4
 
5
- validates :alias, presence: true, format: { with: Record::CNAME_REGEX, message: 'is not a fully qualified domain name' }
5
+ validates :alias, presence: true, format:
6
+ {
7
+ with: Record::CNAME_REGEX,
8
+ message: 'is not a fully qualified domain name',
9
+ }
6
10
  validate :validate_circular_reference
7
11
 
8
12
  def initialize(record)
@@ -5,10 +5,14 @@ module RecordStore
5
5
  LABEL_REGEX = '[a-z0-9](?:-*[a-z0-9])*'
6
6
  DOMAIN_REGEX = /\A#{LABEL_REGEX}(?:\.#{LABEL_REGEX})\z/i
7
7
 
8
- validates :flags, numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than_or_equal_to: 255 }, presence: true
8
+ validates :flags, presence: true, numericality:
9
+ {
10
+ only_integer: true, greater_than_or_equal_to: 0,
11
+ less_than_or_equal_to: 255
12
+ }
9
13
  validates :tag, inclusion: { in: %w(issue issuewild iodef) }, presence: true
10
14
  validate :validate_uri_value, if: :iodef?
11
- validates :value, format: { with: DOMAIN_REGEX, message: 'is not a fully qualified domain name'}, unless: :iodef?
15
+ validates :value, format: { with: DOMAIN_REGEX, message: 'is not a fully qualified domain name' }, unless: :iodef?
12
16
 
13
17
  def initialize(record)
14
18
  super
@@ -37,7 +41,7 @@ module RecordStore
37
41
 
38
42
  def validate_uri_value
39
43
  uri = URI(value)
40
- return if uri.kind_of?(URI::MailTo) || uri.kind_of?(URI::HTTP)
44
+ return if uri.is_a?(URI::MailTo) || uri.is_a?(URI::HTTP)
41
45
  errors.add(:value, "URL scheme should be mailto, http, or https")
42
46
  rescue URI::Error
43
47
  errors.add(:value, "Value should be a valid URI")
@@ -2,7 +2,11 @@ module RecordStore
2
2
  class Record::CNAME < Record
3
3
  attr_accessor :cname
4
4
 
5
- validates :cname, presence: true, format: { with: Record::CNAME_REGEX, message: 'is not a fully qualified domain name' }
5
+ validates :cname, presence: true, format:
6
+ {
7
+ with: Record::CNAME_REGEX,
8
+ message: 'is not a fully qualified domain name',
9
+ }
6
10
  validate :validate_circular_reference
7
11
 
8
12
  def initialize(record)
@@ -2,8 +2,16 @@ module RecordStore
2
2
  class Record::MX < Record
3
3
  attr_accessor :exchange, :preference
4
4
 
5
- validates :preference, numericality: { only_integer: true, greater_than_or_equal_to: 0 }, presence: true
6
- validates :exchange, presence: true, format: { with: Record::FQDN_REGEX, message: 'is not a fully qualified domain name' }
5
+ validates :preference, presence: true, numericality:
6
+ {
7
+ only_integer: true,
8
+ greater_than_or_equal_to: 0,
9
+ }
10
+ validates :exchange, presence: true, format:
11
+ {
12
+ with: Record::FQDN_REGEX,
13
+ message: 'is not a fully qualified domain name',
14
+ }
7
15
 
8
16
  def initialize(record)
9
17
  super
@@ -14,7 +22,7 @@ module RecordStore
14
22
  def rdata
15
23
  {
16
24
  preference: preference,
17
- exchange: exchange
25
+ exchange: exchange,
18
26
  }
19
27
  end
20
28
 
@@ -2,7 +2,11 @@ module RecordStore
2
2
  class Record::NS < Record
3
3
  attr_accessor :nsdname
4
4
 
5
- validates :nsdname, presence: true, format: { with: Record::FQDN_REGEX, message: 'is not a fully qualified domain name' }
5
+ validates :nsdname, presence: true, format:
6
+ {
7
+ with: Record::FQDN_REGEX,
8
+ message: 'is not a fully qualified domain name',
9
+ }
6
10
 
7
11
  def initialize(record)
8
12
  super
@@ -2,10 +2,26 @@ module RecordStore
2
2
  class Record::SRV < Record
3
3
  attr_accessor :priority, :port, :weight, :target
4
4
 
5
- validates :priority, presence: true, numericality: { only_integer: true, greater_than_or_equal_to: 0 }
6
- validates :port, presence: true, numericality: { only_integer: true, greater_than_or_equal_to: 0 }
7
- validates :weight, presence: true, numericality: { only_integer: true, greater_than_or_equal_to: 0 }
8
- validates :target, presence: true, format: { with: Record::CNAME_REGEX, message: 'is not a fully qualified domain name' }
5
+ validates :priority, presence: true, numericality:
6
+ {
7
+ only_integer: true,
8
+ greater_than_or_equal_to: 0,
9
+ }
10
+ validates :port, presence: true, numericality:
11
+ {
12
+ only_integer: true,
13
+ greater_than_or_equal_to: 0,
14
+ }
15
+ validates :weight, presence: true, numericality:
16
+ {
17
+ only_integer: true,
18
+ greater_than_or_equal_to: 0,
19
+ }
20
+ validates :target, presence: true, format:
21
+ {
22
+ with: Record::CNAME_REGEX,
23
+ message: 'is not a fully qualified domain name',
24
+ }
9
25
 
10
26
  def initialize(record)
11
27
  super
@@ -2,7 +2,7 @@ module RecordStore
2
2
  class Record::TXT < Record
3
3
  attr_accessor :txtdata
4
4
 
5
- validates :txtdata, presence: true, length: { maximum: 255 }
5
+ validates :txtdata, presence: true, length: { maximum: 4096 }
6
6
  validate :escaped_semicolons
7
7
 
8
8
  def initialize(record)
@@ -1,3 +1,3 @@
1
1
  module RecordStore
2
- VERSION = '5.5.3'.freeze
2
+ VERSION = '5.5.4'.freeze
3
3
  end
@@ -6,7 +6,11 @@ module RecordStore
6
6
  attr_accessor :name
7
7
  attr_reader :config
8
8
 
9
- validates :name, presence: true, format: { with: Record::FQDN_REGEX, message: 'is not a fully qualified domain name' }
9
+ validates :name, presence: true, format:
10
+ {
11
+ with: Record::FQDN_REGEX,
12
+ message: 'is not a fully qualified domain name',
13
+ }
10
14
  validate :validate_records
11
15
  validate :validate_config
12
16
  validate :validate_all_records_are_unique
@@ -18,14 +22,14 @@ module RecordStore
18
22
 
19
23
  class << self
20
24
  def download(name, provider_name, **write_options)
21
- zone = Zone.new(name: name, config: {providers: [provider_name]})
25
+ zone = Zone.new(name: name, config: { providers: [provider_name] })
22
26
  raise ArgumentError, zone.errors.full_messages.join("\n") unless zone.valid?
23
27
 
24
28
  zone.records = zone.providers.first.retrieve_current_records(zone: name)
25
29
 
26
30
  zone.config = Zone::Config.new(
27
31
  providers: [provider_name],
28
- ignore_patterns: [{type: "NS", fqdn: "#{name}."}],
32
+ ignore_patterns: [{ type: "NS", fqdn: "#{name}." }],
29
33
  supports_alias: (zone.records.map(&:type).include?('ALIAS') || nil)
30
34
  )
31
35
 
@@ -41,13 +45,15 @@ module RecordStore
41
45
  end
42
46
 
43
47
  MAX_PARALLEL_THREADS = 10
44
- def modified(verbose: false)
45
- modified_zones, mutex, zones = [], Mutex.new, self.all
48
+ def modified(verbose: false) # rubocop:disable Lint/UnusedMethodArgument
49
+ modified_zones = []
50
+ mutex = Mutex.new
51
+ zones = all
46
52
 
47
53
  (1..MAX_PARALLEL_THREADS).map do
48
54
  Thread.new do
49
55
  current_zone = nil
50
- while not zones.empty?
56
+ while zones.any?
51
57
  mutex.synchronize { current_zone = zones.shift }
52
58
  mutex.synchronize { modified_zones << current_zone } unless current_zone.unchanged?
53
59
  end
@@ -147,15 +153,15 @@ module RecordStore
147
153
  cname_records = records.select { |record| record.is_a?(Record::CNAME) }
148
154
  cname_records.each do |cname_record|
149
155
  records.each do |record|
150
- if record.fqdn == cname_record.fqdn && record != cname_record
151
- case record.type
152
- when 'SIG', 'NXT', 'KEY'
153
- # this is fine
154
- when 'CNAME'
155
- errors.add(:records, "Multiple CNAME records are defined for #{record.fqdn}: #{record}")
156
- else
157
- errors.add(:records, "A CNAME record is defined for #{cname_record.fqdn}, so this record is not allowed: #{record}")
158
- end
156
+ next unless record.fqdn == cname_record.fqdn && record != cname_record
157
+ case record.type
158
+ when 'SIG', 'NXT', 'KEY'
159
+ # this is fine
160
+ when 'CNAME'
161
+ errors.add(:records, "Multiple CNAME records are defined for #{record.fqdn}: #{record}")
162
+ else
163
+ cname_error = "A CNAME record is defined for #{cname_record.fqdn}, so this record is not allowed: #{record}"
164
+ errors.add(:records, cname_error)
159
165
  end
160
166
  end
161
167
  end
@@ -175,7 +181,7 @@ module RecordStore
175
181
 
176
182
  providers.each do |provider|
177
183
  (record_types - provider.record_types).each do |record_type|
178
- errors.add(:records, "#{record_type} is a not a supported record type in #{provider.to_s}")
184
+ errors.add(:records, "#{record_type} is a not a supported record type in #{provider}")
179
185
  end
180
186
  end
181
187
  end