record_store 5.5.3 → 5.5.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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