record_store 6.3.1 → 6.5.1

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: 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