record_store 6.5.1 → 6.5.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ad3694c8bdf89c030abd7d0b6e4dda77f6b0627783c11d5f96194b18c0be047d
4
- data.tar.gz: 65a8d64e1a5feff467d78297d9ed299d409d2bad617829d99291f9ddd0557558
3
+ metadata.gz: d08c3213971934f91db50a1f602c45e7110ec2f73c71bde5b02dcff1fd2e0731
4
+ data.tar.gz: 478970b2e7adf68a312544f97c0effd775398517340344d2eb6e0fdfc23c3cd9
5
5
  SHA512:
6
- metadata.gz: 257591b26a8b5b58d3752742d43be0acd79268ba0d3f69f2bf09b8469beefc70171900ba04f2b651ffd97d4bed82a49a42c65d002b3cb666e20178ba8d15864c
7
- data.tar.gz: 8a605452495449a6a18e55ea57facb5d582977c21df5b74863fdda8ed185a9eafce3d788f6cab8b680bad019edf152c52548aa1dae7356700cf8026c1d5c5e1e
6
+ metadata.gz: c67e750aedfdf0d8d9e5dc5bbb8ca01c2b5a0cd68a880f67fdc8f2094805b3ee8b231303b2f65c6f1012873f15a23a3383de786b7c44dc62cbf7eca2709058bb
7
+ data.tar.gz: 2d774dae370703a8807d7b91e4dbe50a1186ba920d7df2b8a3bf3276e930fb1f4342a050bdfcc84f8fdfae0231692064e969911452c0631f25809cd2da9aaca5
data/.rubocop.yml CHANGED
@@ -1,19 +1,40 @@
1
- inherit_from:
2
- - https://shopify.github.io/ruby-style-guide/rubocop.yml
1
+ inherit_gem:
2
+ rubocop-shopify: rubocop.yml
3
3
 
4
4
  AllCops:
5
- TargetRubyVersion: 2.4
5
+ TargetRubyVersion: 2.6.4
6
6
  Exclude:
7
7
  - vendor/**/*
8
8
  UseCache: true
9
9
  CacheRootDirectory: tmp/rubocop
10
10
 
11
+ Naming/InclusiveLanguage:
12
+ Enabled: false
13
+
14
+ Style/ClassAndModuleChildren:
15
+ Enabled: false
16
+
11
17
  Style/FrozenStringLiteralComment:
12
18
  Enabled: false
13
19
 
20
+ Style/QuotedSymbols:
21
+ Enabled: false
22
+
14
23
  Style/MethodCallWithArgsParentheses:
15
24
  Include:
16
25
  - '**/*.rb'
17
26
 
18
- Style/ClassAndModuleChildren:
19
- Enabled: false
27
+ Style/StringLiterals:
28
+ Enabled: false
29
+
30
+ Style/StringLiteralsInInterpolation:
31
+ Enabled: false
32
+
33
+ Style/TrailingCommaInArrayLiteral:
34
+ Enabled: false
35
+
36
+ Style/TrailingCommaInHashLiteral:
37
+ Enabled: false
38
+
39
+ Style/WordArray:
40
+ Enabled: false
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 6.5.5
4
+ - Include DNSimple validation error details in exceptions
5
+
6
+ ## 6.5.4
7
+ - Updates config path structure for build pipeline
8
+
9
+ ## 6.5.3
10
+ - Adds check for detecting shadowed records, used when a record being added to a zone in record-store will have no effect because it is shadowed by another record.
11
+
12
+ ## 6.5.2
13
+ - Ensure filters for implicit_records, `except_record` and `conflict_with`, are truly optional [BUGFIX]
14
+
3
15
  ## 6.5.1
4
16
 
5
17
  Add support for a new parameter in the implicit records templates:
@@ -193,14 +193,12 @@ module RecordStore
193
193
 
194
194
  desc 'secrets', 'Decrypts DynECT credentials'
195
195
  def secrets
196
- environment = begin
197
- if ENV['PRODUCTION']
198
- 'production'
199
- elsif ENV['CI']
200
- 'ci'
201
- else
202
- 'dev'
203
- end
196
+ environment = if ENV['PRODUCTION']
197
+ 'production'
198
+ elsif ENV['CI']
199
+ 'ci'
200
+ else
201
+ 'dev'
204
202
  end
205
203
 
206
204
  secrets = %x(ejson decrypt #{RecordStore.secrets_path.sub(/\.json\z/, ".#{environment}.ejson")})
@@ -0,0 +1,20 @@
1
+ module Dnsimple
2
+ class RequestError < Error
3
+ private
4
+
5
+ alias_method :original_message_from, :message_from
6
+
7
+ def message_from(http_response)
8
+ message = original_message_from(http_response)
9
+ return unless json_response?(http_response)
10
+
11
+ base_error = http_response.parsed_response.dig("errors", "base")&.join(", ")
12
+ message += ": #{base_error}" unless base_error.nil?
13
+ message
14
+ end
15
+
16
+ def json_response?(http_response)
17
+ http_response.headers["Content-Type"]&.start_with?("application/json")
18
+ end
19
+ end
20
+ end
@@ -1,5 +1,6 @@
1
1
  require 'dnsimple'
2
2
  require_relative 'dnsimple/patch_api_header'
3
+ require_relative 'dnsimple/patch_request_error_to_include_errors'
3
4
 
4
5
  module RecordStore
5
6
  class Provider::DNSimple < Provider
@@ -20,12 +21,10 @@ module RecordStore
20
21
  def retrieve_current_records(zone:, stdout: $stdout)
21
22
  retry_on_connection_errors do
22
23
  session.zones.all_records(account_id, zone).data.map do |record|
23
- begin
24
- build_from_api(record, zone)
25
- rescue StandardError
26
- stdout.puts "Cannot build record: #{record}"
27
- raise
28
- end
24
+ build_from_api(record, zone)
25
+ rescue StandardError
26
+ stdout.puts "Cannot build record: #{record}"
27
+ raise
29
28
  end.compact
30
29
  end
31
30
  end
@@ -36,11 +36,9 @@ module RecordStore
36
36
  def retrieve_current_records(zone:, stdout: $stdout)
37
37
  session.get_all_records(zone).body.fetch('data').flat_map do |_type, records|
38
38
  records.map do |record_body|
39
- begin
40
- build_from_api(record_body)
41
- rescue StandardError
42
- stdout.puts "Cannot build record: #{record_body}"
43
- end
39
+ build_from_api(record_body)
40
+ rescue StandardError
41
+ stdout.puts "Cannot build record: #{record_body}"
44
42
  end
45
43
  end.compact
46
44
  end
@@ -22,13 +22,11 @@ module RecordStore
22
22
 
23
23
  # Unroll each record set into multiple records
24
24
  record_set.data.map do |record|
25
- begin
26
- record_set_member = record_set.dup
27
- record_set_member.data = [record]
28
- build_from_api(record_set_member)
29
- rescue StandardError
30
- stdout.puts "Cannot build record: #{record}"
31
- end
25
+ record_set_member = record_set.dup
26
+ record_set_member.data = [record]
27
+ build_from_api(record_set_member)
28
+ rescue StandardError
29
+ stdout.puts "Cannot build record: #{record}"
32
30
  end
33
31
  end
34
32
 
@@ -44,12 +42,10 @@ module RecordStore
44
42
  private
45
43
 
46
44
  def session
47
- @dns ||= begin
48
- Google::Cloud::Dns.new(
49
- project_id: secrets.fetch('project_id'),
50
- credentials: Google::Cloud::Dns::Credentials.new(secrets),
51
- )
52
- end
45
+ @dns ||= Google::Cloud::Dns.new(
46
+ project_id: secrets.fetch('project_id'),
47
+ credentials: Google::Cloud::Dns::Credentials.new(secrets),
48
+ )
53
49
  end
54
50
 
55
51
  def secrets
@@ -50,7 +50,7 @@ module RecordStore
50
50
  # Downloads all the records from the provider.
51
51
  #
52
52
  # Returns: an array of `Record` for each record in the provider's zone
53
- def retrieve_current_records(zone:, stdout: $stdout) # rubocop:disable Lint/UnusedMethodArgument
53
+ def retrieve_current_records(zone:, stdout: $stdout)
54
54
  records_for_zone(zone)
55
55
  .flat_map { |short_record| build_from_api(short_record) }
56
56
  .compact
@@ -3,6 +3,7 @@ require 'resolv'
3
3
  module RecordStore
4
4
  class Provider
5
5
  class Error < StandardError; end
6
+
6
7
  class UnparseableBodyError < Error; end
7
8
 
8
9
  class << self
@@ -152,24 +153,22 @@ module RecordStore
152
153
  )
153
154
 
154
155
  loop do
155
- begin
156
- return yield
157
- rescue UnparseableBodyError
158
- raise if max_retries <= 0
159
- max_retries -= 1
160
-
161
- waiter.wait(message: 'Waiting to retry after receiving an unparseable response')
162
- rescue Net::OpenTimeout, Errno::ETIMEDOUT
163
- raise if max_timeouts <= 0
164
- max_timeouts -= 1
165
-
166
- $stderr.puts('Retrying after a connection timeout')
167
- rescue Errno::ECONNRESET
168
- raise if max_conn_resets <= 0
169
- max_conn_resets -= 1
170
-
171
- waiter.wait
172
- end
156
+ return yield
157
+ rescue UnparseableBodyError
158
+ raise if max_retries <= 0
159
+ max_retries -= 1
160
+
161
+ waiter.wait(message: 'Waiting to retry after receiving an unparseable response')
162
+ rescue Net::OpenTimeout, Errno::ETIMEDOUT
163
+ raise if max_timeouts <= 0
164
+ max_timeouts -= 1
165
+
166
+ $stderr.puts('Retrying after a connection timeout')
167
+ rescue Errno::ECONNRESET
168
+ raise if max_conn_resets <= 0
169
+ max_conn_resets -= 1
170
+
171
+ waiter.wait
173
172
  end
174
173
  end
175
174
  end
@@ -1,3 +1,3 @@
1
1
  module RecordStore
2
- VERSION = '6.5.1'.freeze
2
+ VERSION = '6.5.5'.freeze
3
3
  end
@@ -29,11 +29,11 @@ module RecordStore
29
29
 
30
30
  template_file_yaml = YAML.load(template_file).deep_symbolize_keys
31
31
  filters_for_records_to_template = template_file_yaml[:each_record]
32
- filters_for_records_to_exclude = template_file_yaml[:except_record]
32
+ filters_for_records_to_exclude = template_file_yaml[:except_record] || []
33
33
 
34
34
  new(template: ERB.new(template_file),
35
- filters_for_records_to_template: filters_for_records_to_template,
36
- filters_for_records_to_exclude: filters_for_records_to_exclude)
35
+ filters_for_records_to_template: filters_for_records_to_template,
36
+ filters_for_records_to_exclude: filters_for_records_to_exclude)
37
37
  end
38
38
 
39
39
  private
@@ -70,8 +70,9 @@ module RecordStore
70
70
  attr_reader :template, :filters_for_records_to_template, :filters_for_records_to_exclude
71
71
 
72
72
  def should_inject?(template_records:, current_records:)
73
+ conflict_with = template_records[:conflict_with] || []
73
74
  current_records.none? do |record|
74
- template_records[:conflict_with].any? do |filter|
75
+ conflict_with.any? do |filter|
75
76
  record_match?(record: record, filter: filter)
76
77
  end
77
78
  end
@@ -31,7 +31,7 @@ module RecordStore
31
31
  end
32
32
 
33
33
  def write(name, config:, records:, format: :file)
34
- raise ArgumentError, "format must be :directory or :file" unless %i(file directory).include?(format)
34
+ raise ArgumentError, "format must be :directory or :file" unless [:file, :directory].include?(format)
35
35
  name = name.chomp('.')
36
36
  zone_file = "#{RecordStore.zones_path}/#{name}.yml"
37
37
  zone = { name => { config: config.to_hash } }
@@ -21,6 +21,7 @@ module RecordStore
21
21
  validate :validate_no_empty_non_terminal
22
22
  validate :validate_can_handle_alias_records
23
23
  validate :validate_no_duplicate_keys
24
+ validate :validate_zone_record_not_shadowed
24
25
 
25
26
  class << self
26
27
  def download(name, provider_name, **write_options)
@@ -52,7 +53,7 @@ module RecordStore
52
53
  (ENV['RECORD_STORE_MAX_THREADS'] || DEFAULT_MAX_PARALLEL_THREADS).to_i
53
54
  end
54
55
 
55
- def modified(verbose: false) # rubocop:disable Lint/UnusedMethodArgument
56
+ def modified(verbose: false)
56
57
  modified_zones = []
57
58
  mutex = Mutex.new
58
59
  zones = all
@@ -62,7 +63,12 @@ module RecordStore
62
63
  current_zone = nil
63
64
  while zones.any?
64
65
  mutex.synchronize { current_zone = zones.shift }
65
- mutex.synchronize { modified_zones << current_zone } unless current_zone.unchanged?
66
+ break if current_zone.nil? # account for the race between `zones.any?` and `zones.shift`
67
+
68
+ # `unchanged?` is deliberately outside locked context since it's a bit CPU/time heavy
69
+ unless current_zone.unchanged?
70
+ mutex.synchronize { modified_zones << current_zone }
71
+ end
66
72
  end
67
73
  end
68
74
  end.each(&:join)
@@ -79,10 +85,8 @@ module RecordStore
79
85
  end
80
86
 
81
87
  def build_changesets(all: false)
82
- @changesets ||= begin
83
- providers.map do |provider|
84
- Changeset.build_from(provider: provider, zone: self, all: all)
85
- end
88
+ @changesets ||= providers.map do |provider|
89
+ Changeset.build_from(provider: provider, zone: self, all: all)
86
90
  end
87
91
  end
88
92
 
@@ -272,6 +276,26 @@ module RecordStore
272
276
  end
273
277
  end
274
278
 
279
+ def validate_zone_record_not_shadowed
280
+ nameserver_fqdns = records
281
+ .select { |record| record.is_a?(Record::NS) && name != record.fqdn }
282
+ .map { |record| record.fqdn.delete_suffix(".") }
283
+ .uniq
284
+
285
+ nameserver_fqdns.each do |ns_record|
286
+ selected_records = records.reject do |record|
287
+ record.is_a?(Record::NS) && \
288
+ record.fqdn.delete_suffix(".") == ns_record
289
+ end
290
+ selected_records.each do |record|
291
+ normalized_record = record.fqdn.delete_suffix(".")
292
+ next unless normalized_record.end_with?(".#{ns_record}") || normalized_record == ns_record
293
+ errors.add(:records, "Record #{record.fqdn} #{record.type} in Zone #{name} " \
294
+ "is shadowed by #{ns_record} and will be ignored")
295
+ end
296
+ end
297
+ end
298
+
275
299
  def validate_no_empty_non_terminal
276
300
  return unless config.empty_non_terminal_over_wildcard?
277
301
 
data/lib/record_store.rb CHANGED
@@ -44,6 +44,7 @@ module RecordStore
44
44
  class << self
45
45
  attr_writer :secrets_path
46
46
  attr_writer :zones_path
47
+ attr_writer :implicit_records_templates_path
47
48
 
48
49
  def secrets_path
49
50
  @secrets_path ||= File.expand_path(config.fetch('secrets_path'), File.dirname(config_path))
@@ -52,7 +53,7 @@ module RecordStore
52
53
  def zones_path
53
54
  @zones_path ||= Pathname.new(
54
55
  File.expand_path(config.fetch('zones_path'),
55
- File.dirname(config_path)),
56
+ File.dirname(config_path)),
56
57
  ).realpath.to_s
57
58
  end
58
59
 
@@ -63,7 +64,7 @@ module RecordStore
63
64
  def implicit_records_templates_path
64
65
  @implicit_records_templates_path ||= Pathname.new(
65
66
  File.expand_path(config.fetch('implicit_records_templates_path'),
66
- File.dirname(config_path)),
67
+ File.dirname(config_path)),
67
68
  ).realpath.to_s
68
69
  end
69
70
 
data/record_store.gemspec CHANGED
@@ -48,6 +48,7 @@ Gem::Specification.new do |spec|
48
48
  spec.add_development_dependency 'vcr'
49
49
  spec.add_development_dependency 'pry'
50
50
  spec.add_development_dependency 'webmock'
51
- spec.add_development_dependency 'rubocop', '~> 1.0.0'
51
+ spec.add_development_dependency 'rubocop', '~> 1.18.0'
52
+ spec.add_development_dependency 'rubocop-shopify', '~> 2.2.0'
52
53
  spec.add_development_dependency 'minitest-focus'
53
54
  end
@@ -5,7 +5,7 @@ dynect.example.com:
5
5
  ignore_patterns:
6
6
  - type: NS
7
7
  fqdn: dynect.example.com.
8
- implicit_record_templates:
8
+ implicit_records_templates:
9
9
  - implicit_example.yml.erb
10
10
  records:
11
11
  - type: A
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: record_store
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.5.1
4
+ version: 6.5.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Willem van Bergen
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2021-08-09 00:00:00.000000000 Z
12
+ date: 2021-11-15 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: thor
@@ -303,14 +303,28 @@ dependencies:
303
303
  requirements:
304
304
  - - "~>"
305
305
  - !ruby/object:Gem::Version
306
- version: 1.0.0
306
+ version: 1.18.0
307
307
  type: :development
308
308
  prerelease: false
309
309
  version_requirements: !ruby/object:Gem::Requirement
310
310
  requirements:
311
311
  - - "~>"
312
312
  - !ruby/object:Gem::Version
313
- version: 1.0.0
313
+ version: 1.18.0
314
+ - !ruby/object:Gem::Dependency
315
+ name: rubocop-shopify
316
+ requirement: !ruby/object:Gem::Requirement
317
+ requirements:
318
+ - - "~>"
319
+ - !ruby/object:Gem::Version
320
+ version: 2.2.0
321
+ type: :development
322
+ prerelease: false
323
+ version_requirements: !ruby/object:Gem::Requirement
324
+ requirements:
325
+ - - "~>"
326
+ - !ruby/object:Gem::Version
327
+ version: 2.2.0
314
328
  - !ruby/object:Gem::Dependency
315
329
  name: minitest-focus
316
330
  requirement: !ruby/object:Gem::Requirement
@@ -357,6 +371,7 @@ files:
357
371
  - lib/record_store/provider.rb
358
372
  - lib/record_store/provider/dnsimple.rb
359
373
  - lib/record_store/provider/dnsimple/patch_api_header.rb
374
+ - lib/record_store/provider/dnsimple/patch_request_error_to_include_errors.rb
360
375
  - lib/record_store/provider/dynect.rb
361
376
  - lib/record_store/provider/google_cloud_dns.rb
362
377
  - lib/record_store/provider/ns1.rb