record_store 6.3.1 → 6.5.1

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: f6875c27f0cf86510b733a58f668d44560eeb40e7c8b6c9438183fee175bc514
4
- data.tar.gz: 3599cc9903a5afe46d544f8aeef349961fdc2d95011973325bbdcc75972c56c5
3
+ metadata.gz: ad3694c8bdf89c030abd7d0b6e4dda77f6b0627783c11d5f96194b18c0be047d
4
+ data.tar.gz: 65a8d64e1a5feff467d78297d9ed299d409d2bad617829d99291f9ddd0557558
5
5
  SHA512:
6
- metadata.gz: 9a6def9342919457cc71fdda4691c92310450dfc28a476eee6d353b73798b8d1b612b4775982704b0c44b2e657149a02f0e96330796b03d591619ad5930509ae
7
- data.tar.gz: 42183ae0ecd31e27a2d40977ecdda6cb51c0368ff24ac3a2de4411ab016809c5551ee3faeb50eb53503be059ece4bc41889e6c73d3e1f0ab53e7ab9290563628
6
+ metadata.gz: 257591b26a8b5b58d3752742d43be0acd79268ba0d3f69f2bf09b8469beefc70171900ba04f2b651ffd97d4bed82a49a42c65d002b3cb666e20178ba8d15864c
7
+ data.tar.gz: 8a605452495449a6a18e55ea57facb5d582977c21df5b74863fdda8ed185a9eafce3d788f6cab8b680bad019edf152c52548aa1dae7356700cf8026c1d5c5e1e
@@ -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
@@ -195,6 +195,7 @@ Style/FrozenStringLiteralComment:
195
195
  SupportedStyles:
196
196
  - always
197
197
  - never
198
+ SafeAutoCorrect: true
198
199
 
199
200
  Style/GlobalVars:
200
201
  AllowedVariables: []
@@ -264,7 +265,7 @@ Style/MethodCallWithArgsParentheses:
264
265
  - raise
265
266
  - puts
266
267
  Exclude:
267
- - Gemfile
268
+ - '**/Gemfile'
268
269
 
269
270
  Style/MethodDefParentheses:
270
271
  EnforcedStyle: require_parentheses
@@ -577,6 +578,7 @@ Layout/BlockEndNewline:
577
578
 
578
579
  Style/CaseEquality:
579
580
  Enabled: true
581
+ AllowOnConstant: true
580
582
 
581
583
  Style/CharacterLiteral:
582
584
  Enabled: true
@@ -659,6 +661,9 @@ Style/IfWithSemicolon:
659
661
  Style/IdenticalConditionalBranches:
660
662
  Enabled: true
661
663
 
664
+ Layout/IndentationStyle:
665
+ Enabled: true
666
+
662
667
  Style/InfiniteLoop:
663
668
  Enabled: true
664
669
 
@@ -803,9 +808,6 @@ Layout/SpaceInsideRangeLiteral:
803
808
  Style/SymbolLiteral:
804
809
  Enabled: true
805
810
 
806
- Layout/IndentationStyle:
807
- Enabled: true
808
-
809
811
  Layout/TrailingWhitespace:
810
812
  Enabled: true
811
813
 
@@ -834,7 +836,7 @@ Style/ZeroLengthPredicate:
834
836
  Enabled: true
835
837
 
836
838
  Layout/HeredocIndentation:
837
- EnforcedStyle: squiggly
839
+ Enabled: true
838
840
 
839
841
  Lint/AmbiguousOperator:
840
842
  Enabled: true
@@ -1015,3 +1017,6 @@ Style/ModuleFunction:
1015
1017
 
1016
1018
  Lint/OrderedMagicComments:
1017
1019
  Enabled: true
1020
+
1021
+ Lint/DeprecatedOpenSSLConstant:
1022
+ Enabled: true
data/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 6.5.1
4
+
5
+ Add support for a new parameter in the implicit records templates:
6
+
7
+ - `except_record`: the template will *NOT* generate the `injected_records` for records matching `except_record`, even if they matched `each_record`.
8
+
9
+ 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
10
+ 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
11
+ `each_record` fields.
12
+
13
+ ## 6.5.0
14
+
15
+ ...
16
+
17
+ ## 6.4.0
18
+
19
+ Add support for injecting implicit records into a zone based on a pre-configured template. Brief overview of template keys:
20
+
21
+ - `each_record`: the template will generate all `injected_records` in the template for each Record in the Zone that matches criteria listed here.
22
+ - `conflict_with`: if any Record in the Zone matches the criteria listed here, the template will avoid injecting any of its generated implicit records into the zone
23
+ - `injected_records`: the implicit records that will be generated by the template for every Zone Record that matches the `each_record` criteria
24
+
3
25
  ## 6.3.1
4
26
  - Improve resiliency in the face of temporary provider outages [BUGFIX]
5
27
 
data/README.md CHANGED
@@ -116,6 +116,13 @@ When running `bin/record-store apply`, a `Changeset` is generated by comparing t
116
116
 
117
117
  Record store attempts to parallelize some of the bulk zone fetching operations. It does so by spawning multiple threads (default: 10). This value can be configured by setting the RECORD_STORE_MAX_THREADS environment variable to a positive integer value.
118
118
 
119
+
120
+ ### Templates
121
+
122
+ Record Store supports injecting implicit records into a zone based on templates. Each `Zone` can have a list of `Zone::Config::ImplicitRecordTemplate` in its `Zone::Config` that can define their own criteria for which records they use for generating implicit template records, which records conflict with the template-generated records, and what the template-generated records look like. These records are injected at the time of `Zone` initialization within `Zone#build_records`.
123
+
124
+ Templates can help reduce zone file bloat where instead of defining many generic literals within the zone file for a given criteria zone record, these generic records can be implicitly injected into the `Zone` at the time of initialization based on information provided within the template.
125
+
119
126
  ----
120
127
 
121
128
  # Development
data/bin/console CHANGED
@@ -6,6 +6,7 @@ require 'record_store'
6
6
 
7
7
  RecordStore.zones_path = File.expand_path('../../dev/zones', __FILE__)
8
8
  RecordStore.config_path = File.expand_path('../../dev/config.yml', __FILE__)
9
+ RecordStore.implicit_records_templates_path = File.expand_path('../../dev/templates/implicit_records', __FILE__)
9
10
 
10
11
  require 'pry'
11
12
  binding.pry(RecordStore) # rubocop:disable Lint/Debugger
data/lib/record_store.rb CHANGED
@@ -28,6 +28,7 @@ require 'record_store/zone/yaml_definitions'
28
28
  require 'record_store/zone'
29
29
  require 'record_store/zone/config'
30
30
  require 'record_store/zone/config/ignore_pattern'
31
+ require 'record_store/zone/config/implicit_record_template'
31
32
  require 'record_store/changeset'
32
33
  require 'record_store/provider'
33
34
  require 'record_store/provider/dynect'
@@ -59,6 +60,13 @@ module RecordStore
59
60
  @config_path ||= File.expand_path('config.yml', Dir.pwd)
60
61
  end
61
62
 
63
+ def implicit_records_templates_path
64
+ @implicit_records_templates_path ||= Pathname.new(
65
+ File.expand_path(config.fetch('implicit_records_templates_path'),
66
+ File.dirname(config_path)),
67
+ ).realpath.to_s
68
+ end
69
+
62
70
  def config_path=(config_path)
63
71
  @config = @zones_path = @secrets_path = nil
64
72
  @config_path = config_path
@@ -1,3 +1,3 @@
1
1
  module RecordStore
2
- VERSION = '6.3.1'.freeze
2
+ VERSION = '6.5.1'.freeze
3
3
  end
@@ -20,6 +20,7 @@ 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
23
24
 
24
25
  class << self
25
26
  def download(name, provider_name, **write_options)
@@ -70,10 +71,11 @@ module RecordStore
70
71
  end
71
72
  end
72
73
 
73
- def initialize(name:, records: [], config: {})
74
+ def initialize(name:, records: [], config: {}, abstract_syntax_trees: {})
74
75
  @name = Record.ensure_ends_with_dot(name)
75
76
  @config = RecordStore::Zone::Config.new(config.deep_symbolize_keys)
76
77
  @records = build_records(records)
78
+ @abstract_syntax_trees = abstract_syntax_trees
77
79
  end
78
80
 
79
81
  def build_changesets(all: false)
@@ -189,7 +191,13 @@ module RecordStore
189
191
  end
190
192
 
191
193
  def build_records(records)
192
- records.map { |record| Record.build_from_yaml_definition(record) }
194
+ all_records = records.map { |record| Record.build_from_yaml_definition(record) }
195
+
196
+ config.implicit_records_templates.each do |template|
197
+ all_records.push(*template.generate_records_to_inject(current_records: all_records))
198
+ end
199
+
200
+ all_records
193
201
  end
194
202
 
195
203
  def validate_records
@@ -301,5 +309,34 @@ module RecordStore
301
309
 
302
310
  errors.add(:records, "ALIAS record should be defined on the root of the zone: #{alias_record}")
303
311
  end
312
+
313
+ def validate_no_duplicate_keys
314
+ @abstract_syntax_trees.each do |filename, ast|
315
+ validate_no_duplicate_keys_in_node(filename, ast)
316
+ end
317
+ end
318
+
319
+ def validate_no_duplicate_keys_in_node(filename, node)
320
+ if node.mapping?
321
+ keys = node
322
+ .children
323
+ .each_slice(2)
324
+ .map(&:first)
325
+ .map(&:value)
326
+ .sort
327
+ dup_keys = keys
328
+ .find_all { |k| keys.count(k) > 1 }
329
+ .uniq
330
+ unless dup_keys.empty?
331
+ location = "#{File.basename(filename)}:#{node.start_line}"
332
+ description = "multiple definitions for keys #{dup_keys}"
333
+ errors.add(:records, "#{location}: #{description}")
334
+ end
335
+ end
336
+
337
+ node.children&.each do |child|
338
+ validate_no_duplicate_keys_in_node(filename, child)
339
+ end
340
+ end
304
341
  end
305
342
  end
@@ -3,15 +3,17 @@ module RecordStore
3
3
  class Config
4
4
  include ActiveModel::Validations
5
5
 
6
- attr_reader :ignore_patterns, :providers, :supports_alias
6
+ attr_reader :ignore_patterns, :providers, :supports_alias, :implicit_records_templates
7
7
 
8
8
  validate :validate_zone_config
9
9
 
10
- def initialize(ignore_patterns: [], providers: nil, supports_alias: nil)
10
+ def initialize(ignore_patterns: [], providers: nil, supports_alias: nil, implicit_records_templates: [])
11
11
  @ignore_patterns = ignore_patterns.map do |ignore_pattern|
12
12
  Zone::Config::IgnorePattern.new(ignore_pattern)
13
13
  end
14
-
14
+ @implicit_records_templates = implicit_records_templates.map do |filename|
15
+ Zone::Config::ImplicitRecordTemplate.from_file(filename: filename)
16
+ end
15
17
  @providers = providers
16
18
  @supports_alias = supports_alias
17
19
  end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+ module RecordStore
3
+ class Zone
4
+ class Config
5
+ class TemplateContext
6
+ def self.build(record:, current_records:)
7
+ new(record: record, current_records: current_records)
8
+ end
9
+
10
+ def initialize(record:, current_records:)
11
+ @record = record
12
+ @current_records = current_records
13
+ end
14
+
15
+ def fetch_binding
16
+ binding
17
+ end
18
+
19
+ private
20
+
21
+ attr_reader :record, :current_records
22
+ end
23
+
24
+ class ImplicitRecordTemplate
25
+ class << self
26
+ def from_file(filename:)
27
+ filepath = template_filepath_for(filename: filename)
28
+ template_file = File.read(filepath)
29
+
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)
37
+ end
38
+
39
+ private
40
+
41
+ def template_filepath_for(filename:)
42
+ "#{RecordStore.implicit_records_templates_path}/#{filename}"
43
+ end
44
+ end
45
+
46
+ def initialize(template:, filters_for_records_to_template:, filters_for_records_to_exclude:)
47
+ @template = template
48
+ @filters_for_records_to_template = filters_for_records_to_template
49
+ @filters_for_records_to_exclude = filters_for_records_to_exclude
50
+ end
51
+
52
+ def generate_records_to_inject(current_records:)
53
+ current_records
54
+ .select { |record| should_template?(record: record) }
55
+ .map { |record| template_record_for(record: record, current_records: current_records) }
56
+ .each_with_object([]) do |template_records, records_to_inject|
57
+ next unless should_inject?(
58
+ template_records: template_records,
59
+ current_records: current_records + records_to_inject
60
+ )
61
+
62
+ records_to_inject.push(
63
+ *template_records.fetch(:injected_records, []).map { |r_yml| Record.build_from_yaml_definition(r_yml) }
64
+ )
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ attr_reader :template, :filters_for_records_to_template, :filters_for_records_to_exclude
71
+
72
+ def should_inject?(template_records:, current_records:)
73
+ current_records.none? do |record|
74
+ template_records[:conflict_with].any? do |filter|
75
+ record_match?(record: record, filter: filter)
76
+ end
77
+ end
78
+ end
79
+
80
+ def should_template?(record:)
81
+ filters_for_records_to_template.any? { |filter| record_match?(record: record, filter: filter) } && \
82
+ filters_for_records_to_exclude.none? { |filter| record_match?(record: record, filter: filter) }
83
+ end
84
+
85
+ def record_match?(record:, filter:)
86
+ filter.all? do |key, value|
87
+ if value.is_a?(Regexp)
88
+ value.match(record.public_send(key))
89
+ else
90
+ record.public_send(key) == value
91
+ end
92
+ end
93
+ end
94
+
95
+ def template_record_for(record:, current_records:)
96
+ context = TemplateContext.build(record: record, current_records: current_records)
97
+
98
+ YAML.load(
99
+ template.result(context.fetch_binding)
100
+ ).deep_symbolize_keys
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -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)
data/record_store.gemspec CHANGED
@@ -48,6 +48,6 @@ 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', '0.89.1'
51
+ spec.add_development_dependency 'rubocop', '~> 1.0.0'
52
52
  spec.add_development_dependency 'minitest-focus'
53
53
  end
data/template/config.yml CHANGED
@@ -1,2 +1,3 @@
1
1
  zones_path: zones/
2
2
  secrets_path: secrets.json
3
+ implicit_records_templates_path: templates/implicit_records/
@@ -0,0 +1,15 @@
1
+ each_record:
2
+ - type: A
3
+ fqdn: abc.123.com.
4
+ - type: TXT
5
+ except_record:
6
+ - fqdn: !ruby/regexp /^_/
7
+ conflict_with:
8
+ - type: TXT
9
+ fqdn: <%= record.fqdn %>.more.domain.com.
10
+ injected_records:
11
+ - type: CNAME
12
+ ttl: 3600
13
+ fqdn: <%= record.fqdn %>.added.information.com
14
+ cname: <%= record.fqdn %>.more.added.information.com.
15
+
@@ -5,6 +5,8 @@ dynect.example.com:
5
5
  ignore_patterns:
6
6
  - type: NS
7
7
  fqdn: dynect.example.com.
8
+ implicit_record_templates:
9
+ - implicit_example.yml.erb
8
10
  records:
9
11
  - type: A
10
12
  fqdn: a-record.dynect.example.com.
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.3.1
4
+ version: 6.5.1
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-10-09 00:00:00.000000000 Z
12
+ date: 2021-08-09 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: thor
@@ -301,16 +301,16 @@ dependencies:
301
301
  name: rubocop
302
302
  requirement: !ruby/object:Gem::Requirement
303
303
  requirements:
304
- - - '='
304
+ - - "~>"
305
305
  - !ruby/object:Gem::Version
306
- version: 0.89.1
306
+ version: 1.0.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: 0.89.1
313
+ version: 1.0.0
314
314
  - !ruby/object:Gem::Dependency
315
315
  name: minitest-focus
316
316
  requirement: !ruby/object:Gem::Requirement
@@ -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"
@@ -380,6 +381,7 @@ files:
380
381
  - lib/record_store/zone.rb
381
382
  - lib/record_store/zone/config.rb
382
383
  - lib/record_store/zone/config/ignore_pattern.rb
384
+ - lib/record_store/zone/config/implicit_record_template.rb
383
385
  - lib/record_store/zone/yaml_definitions.rb
384
386
  - record_store.gemspec
385
387
  - shipit.rubygems.yml
@@ -389,6 +391,7 @@ files:
389
391
  - template/bin/test
390
392
  - template/config.yml
391
393
  - template/secrets.json
394
+ - template/templates/implicit_records/implicit_example.yml.erb
392
395
  - template/zones/dnsimple.example.com.yml
393
396
  - template/zones/dnsimple.example.com/A__a-record.yml
394
397
  - template/zones/dnsimple.example.com/TXT__marco.yml
@@ -414,7 +417,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
414
417
  - !ruby/object:Gem::Version
415
418
  version: '0'
416
419
  requirements: []
417
- rubygems_version: 3.0.3
420
+ rubygems_version: 3.2.20
418
421
  signing_key:
419
422
  specification_version: 4
420
423
  summary: Manage DNS using git