record_store 6.4.1 → 6.5.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2537c52a9350b969e64d8fab36f1281813e3d1db528accd4ee0700cdc6217b80
4
- data.tar.gz: 8561a6ea4212f180a47240d5f1234f43e1d1896e620f2ea0acb427e06b2306fc
3
+ metadata.gz: 9af43560e4a84302824fb5b1abef5476bc0995e752d3960de068b45949b1d154
4
+ data.tar.gz: 4bc999da542a5484d7cf51a7bb652593c323fecac6d79d8e73ccbb62bd638ea0
5
5
  SHA512:
6
- metadata.gz: 81048f68e0c0710fa7d61bcc25f97583e0b73dd7a0debea7281a8e22403a46f8fb245607d1f9a74ee8a6412915184d4c1faf7a5178fd3a666b64217201715115
7
- data.tar.gz: 88ed41158b07af0b2efed051d2794e54efacc90e36457b51aacb9e53e14307c15fcde259f5beda9b4df5056d7fd517fa52852e5b828d264d90433ccf62464977
6
+ metadata.gz: d72d0a1bffea56a12070eab25df96cd120f00107779063ae651a141310b369764eeaa5fa8993d77c67f81e2f4e98e16641e6d8dacde4f35fa09be4a6fe08236e
7
+ data.tar.gz: e84f52373f10caa1fa464845a13a33383c7461392f69c88c17c032f9d91e8b96ffcd87fc9f1fc798478b05b2de2660629e2d6d4b89e232b405d80009676eac5b
@@ -0,0 +1,28 @@
1
+ name: CI
2
+
3
+ on: [push, pull_request]
4
+
5
+ env:
6
+ SRB_SKIP_GEM_RBIS: true
7
+
8
+ jobs:
9
+ build:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ fail-fast: false
13
+ matrix:
14
+ ruby: [ 2.7.1 ]
15
+ name: Test Ruby ${{ matrix.ruby }}
16
+ steps:
17
+ - uses: actions/checkout@v2
18
+ - name: Set up Ruby
19
+ uses: ruby/setup-ruby@v1
20
+ with:
21
+ ruby-version: ${{ matrix.ruby }}
22
+ bundler-cache: true
23
+ - name: rubocop
24
+ run: bin/rubocop --version && bin/rubocop
25
+ - name: setup
26
+ run: bin/setup
27
+ - name: test
28
+ run: bundle exec rake test
data/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 6.5.3
4
+ - 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.
5
+
6
+ ## 6.5.2
7
+ - Ensure filters for implicit_records, `except_record` and `conflict_with`, are truly optional [BUGFIX]
8
+
9
+ ## 6.5.1
10
+
11
+ Add support for a new parameter in the implicit records templates:
12
+
13
+ - `except_record`: the template will *NOT* generate the `injected_records` for records matching `except_record`, even if they matched `each_record`.
14
+
15
+ Also added support for regular expressions in the matching, using the already available `!ruby/regexp` text in a YAML value. The object loaded from the YAML
16
+ will be of `Regexp` type, and will thus be used to _match_ the value, instead of being identical only. This is supported in both `except_record` and
17
+ `each_record` fields.
18
+
19
+ ## 6.5.0
20
+
21
+ ...
22
+
3
23
  ## 6.4.0
4
24
 
5
25
  Add support for injecting implicit records into a zone based on a pre-configured template. Brief overview of template keys:
@@ -7,7 +7,7 @@ module RecordStore
7
7
 
8
8
  def initialize(*args)
9
9
  super
10
- RecordStore.config_path = options.fetch('config', "#{Dir.pwd}/config.yml")
10
+ RecordStore.config_path = options.fetch('config', "#{Dir.pwd}/template/config.yml")
11
11
  end
12
12
 
13
13
  def self.exit_on_failure?
@@ -1,3 +1,3 @@
1
1
  module RecordStore
2
- VERSION = '6.4.1'.freeze
2
+ VERSION = '6.5.3'.freeze
3
3
  end
@@ -26,9 +26,14 @@ module RecordStore
26
26
  def from_file(filename:)
27
27
  filepath = template_filepath_for(filename: filename)
28
28
  template_file = File.read(filepath)
29
- filters_for_records_to_template = YAML.load(template_file).deep_symbolize_keys[:each_record]
30
29
 
31
- new(template: ERB.new(template_file), filters_for_records_to_template: filters_for_records_to_template)
30
+ template_file_yaml = YAML.load(template_file).deep_symbolize_keys
31
+ filters_for_records_to_template = template_file_yaml[:each_record]
32
+ filters_for_records_to_exclude = template_file_yaml[:except_record] || []
33
+
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)
32
37
  end
33
38
 
34
39
  private
@@ -38,9 +43,10 @@ module RecordStore
38
43
  end
39
44
  end
40
45
 
41
- def initialize(template:, filters_for_records_to_template:)
46
+ def initialize(template:, filters_for_records_to_template:, filters_for_records_to_exclude:)
42
47
  @template = template
43
48
  @filters_for_records_to_template = filters_for_records_to_template
49
+ @filters_for_records_to_exclude = filters_for_records_to_exclude
44
50
  end
45
51
 
46
52
  def generate_records_to_inject(current_records:)
@@ -61,23 +67,29 @@ module RecordStore
61
67
 
62
68
  private
63
69
 
64
- attr_reader :template, :filters_for_records_to_template
70
+ attr_reader :template, :filters_for_records_to_template, :filters_for_records_to_exclude
65
71
 
66
72
  def should_inject?(template_records:, current_records:)
73
+ conflict_with = template_records[:conflict_with] || []
67
74
  current_records.none? do |record|
68
- template_records[:conflict_with].any? do |filter|
75
+ conflict_with.any? do |filter|
69
76
  record_match?(record: record, filter: filter)
70
77
  end
71
78
  end
72
79
  end
73
80
 
74
81
  def should_template?(record:)
75
- filters_for_records_to_template.any? { |filter| record_match?(record: record, filter: filter) }
82
+ filters_for_records_to_template.any? { |filter| record_match?(record: record, filter: filter) } && \
83
+ filters_for_records_to_exclude.none? { |filter| record_match?(record: record, filter: filter) }
76
84
  end
77
85
 
78
86
  def record_match?(record:, filter:)
79
87
  filter.all? do |key, value|
80
- record.public_send(key) == value
88
+ if value.is_a?(Regexp)
89
+ value.match(record.public_send(key))
90
+ else
91
+ record.public_send(key) == value
92
+ end
81
93
  end
82
94
  end
83
95
 
@@ -66,7 +66,10 @@ module RecordStore
66
66
  Dir["#{dir}/#{name}/*__*.yml"].each do |record_file|
67
67
  definition['records'] += load_yml_record_definitions(name, record_file)
68
68
  end
69
- Zone.new(name: name, records: definition['records'], config: definition['config'])
69
+
70
+ asts = { filename => Psych.parse_file(filename) }
71
+
72
+ Zone.new(name: name, records: definition['records'], config: definition['config'], abstract_syntax_trees: asts)
70
73
  end
71
74
 
72
75
  def load_yml_record_definitions(name, record_file)
@@ -20,6 +20,8 @@ module RecordStore
20
20
  validate :validate_provider_can_handle_zone_records
21
21
  validate :validate_no_empty_non_terminal
22
22
  validate :validate_can_handle_alias_records
23
+ validate :validate_no_duplicate_keys
24
+ validate :validate_zone_record_not_shadowed
23
25
 
24
26
  class << self
25
27
  def download(name, provider_name, **write_options)
@@ -61,7 +63,12 @@ module RecordStore
61
63
  current_zone = nil
62
64
  while zones.any?
63
65
  mutex.synchronize { current_zone = zones.shift }
64
- 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
65
72
  end
66
73
  end
67
74
  end.each(&:join)
@@ -70,10 +77,11 @@ module RecordStore
70
77
  end
71
78
  end
72
79
 
73
- def initialize(name:, records: [], config: {})
80
+ def initialize(name:, records: [], config: {}, abstract_syntax_trees: {})
74
81
  @name = Record.ensure_ends_with_dot(name)
75
82
  @config = RecordStore::Zone::Config.new(config.deep_symbolize_keys)
76
83
  @records = build_records(records)
84
+ @abstract_syntax_trees = abstract_syntax_trees
77
85
  end
78
86
 
79
87
  def build_changesets(all: false)
@@ -270,6 +278,26 @@ module RecordStore
270
278
  end
271
279
  end
272
280
 
281
+ def validate_zone_record_not_shadowed
282
+ nameserver_fqdns = records
283
+ .select { |record| record.is_a?(Record::NS) && name != record.fqdn }
284
+ .map { |record| record.fqdn.delete_suffix(".") }
285
+ .uniq
286
+
287
+ nameserver_fqdns.each do |ns_record|
288
+ selected_records = records.reject do |record|
289
+ record.is_a?(Record::NS) && \
290
+ record.fqdn.delete_suffix(".") == ns_record
291
+ end
292
+ selected_records.each do |record|
293
+ normalized_record = record.fqdn.delete_suffix(".")
294
+ next unless normalized_record.end_with?(".#{ns_record}") || normalized_record == ns_record
295
+ errors.add(:records, "Record #{record.fqdn} #{record.type} in Zone #{name} " \
296
+ "is shadowed by #{ns_record} and will be ignored")
297
+ end
298
+ end
299
+ end
300
+
273
301
  def validate_no_empty_non_terminal
274
302
  return unless config.empty_non_terminal_over_wildcard?
275
303
 
@@ -307,5 +335,34 @@ module RecordStore
307
335
 
308
336
  errors.add(:records, "ALIAS record should be defined on the root of the zone: #{alias_record}")
309
337
  end
338
+
339
+ def validate_no_duplicate_keys
340
+ @abstract_syntax_trees.each do |filename, ast|
341
+ validate_no_duplicate_keys_in_node(filename, ast)
342
+ end
343
+ end
344
+
345
+ def validate_no_duplicate_keys_in_node(filename, node)
346
+ if node.mapping?
347
+ keys = node
348
+ .children
349
+ .each_slice(2)
350
+ .map(&:first)
351
+ .map(&:value)
352
+ .sort
353
+ dup_keys = keys
354
+ .find_all { |k| keys.count(k) > 1 }
355
+ .uniq
356
+ unless dup_keys.empty?
357
+ location = "#{File.basename(filename)}:#{node.start_line}"
358
+ description = "multiple definitions for keys #{dup_keys}"
359
+ errors.add(:records, "#{location}: #{description}")
360
+ end
361
+ end
362
+
363
+ node.children&.each do |child|
364
+ validate_no_duplicate_keys_in_node(filename, child)
365
+ end
366
+ end
310
367
  end
311
368
  end
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))
@@ -2,6 +2,8 @@ each_record:
2
2
  - type: A
3
3
  fqdn: abc.123.com.
4
4
  - type: TXT
5
+ except_record:
6
+ - fqdn: !ruby/regexp /^_/
5
7
  conflict_with:
6
8
  - type: TXT
7
9
  fqdn: <%= record.fqdn %>.more.domain.com.
@@ -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.4.1
4
+ version: 6.5.3
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: 2020-12-02 00:00:00.000000000 Z
12
+ date: 2021-09-23 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: thor
@@ -335,6 +335,7 @@ executables:
335
335
  extensions: []
336
336
  extra_rdoc_files: []
337
337
  files:
338
+ - ".github/workflows/ci.yml"
338
339
  - ".gitignore"
339
340
  - ".rubocop-https---shopify-github-io-ruby-style-guide-rubocop-yml"
340
341
  - ".rubocop.yml"
@@ -416,7 +417,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
416
417
  - !ruby/object:Gem::Version
417
418
  version: '0'
418
419
  requirements: []
419
- rubygems_version: 3.0.3
420
+ rubygems_version: 3.2.20
420
421
  signing_key:
421
422
  specification_version: 4
422
423
  summary: Manage DNS using git