record_store 6.2.1 → 6.5.0

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: f2933700da7288ff5085d9865364776031896ea9efada8cf75fea37e5edb27b1
4
- data.tar.gz: 5d1420e6df2c086d7d98ce655f8b8512bd65467ff4825a6f00b628b16b5fa180
3
+ metadata.gz: 7f5d50f38de714c7b8d6d316070fc9d6bca5ac4d40751629c3c565866178d628
4
+ data.tar.gz: 14e3171ad53e5903404fcda9404bbfa109ec63db0c9253d8f3f7df76ee0e0ff1
5
5
  SHA512:
6
- metadata.gz: eee575d27faa042e9db956aa842b5d0f8a35aac56784cff7369a56970554f0430cdec4dbd67d904364a97bf5f53d5769dbf3ff8f46db4c5ce3822a8d1c39412d
7
- data.tar.gz: 796fcd383e7df7621f21b63356c7634afb0a8a71447f0775801ec93297bd5754f48250fd28390cbfde412fe1a51d85329d8f0fd0f5358073810a98fa1ad08e2b
6
+ metadata.gz: ac87302ec7f2d9e1afcbac7af6ef340a7a6fdd7d503a2f941fb7e7f021a46eb3a2b071841a152b264ba1956c50aa45141853a3bfdda4e8101a5e226ddec595ac
7
+ data.tar.gz: 17fdee4d8ca5b4e9692e6a3718cdbb143e076890b2752a9dd4516c110835edbd73208056c8be8bfc5d18b926d97050d33300a04c29639d77cdc1429b2caf41cc
@@ -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/.travis.yml CHANGED
@@ -1,7 +1,7 @@
1
1
  cache: bundler
2
2
  language: ruby
3
3
  rvm:
4
- - 2.4.3
4
+ - 2.6.4
5
5
 
6
6
  before_install:
7
7
  - gem update --system
data/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 6.4.0
4
+
5
+ Add support for injecting implicit records into a zone based on a pre-configured template. Brief overview of template keys:
6
+
7
+ - `each_record`: the template will generate all `injected_records` in the template for each Record in the Zone that matches criteria listed here.
8
+ - `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
9
+ - `injected_records`: the implicit records that will be generated by the template for every Zone Record that matches the `each_record` criteria
10
+
11
+ ## 6.3.1
12
+ - Improve resiliency in the face of temporary provider outages [BUGFIX]
13
+
14
+ ## 6.3.0
15
+ - Support for configurable number of threads via environment variable [FEATURE]
16
+
17
+ ## 6.2.1
18
+ - Improved error reporting after timeouts [FEATURE]
19
+
20
+ ## 6.2.0
21
+ - Add validation for non-terminal conflict with wildcard [FEATURE]
22
+
23
+ ## 6.1.2
24
+ - Retry on connection errors [FEATURE]
25
+
3
26
  ## 6.1.1
4
27
  - Emit messages when waiting for rate-limit to elapse for DNSimple and NS1 providers, so deployment does not timeout [BUGFIX]
5
28
 
data/README.md CHANGED
@@ -112,6 +112,17 @@ Changesets are how Record Store knows what updates to make. A `Changeset` is gen
112
112
 
113
113
  When running `bin/record-store apply`, a `Changeset` is generated by comparing the current records in a zone's YAML file with the records the provider defines. A zone's YAML file is always considered the primary source of truth.
114
114
 
115
+ ### Parallelism
116
+
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
+
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
+
115
126
  ----
116
127
 
117
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/dev.yml CHANGED
@@ -2,7 +2,7 @@
2
2
  name: recordstore
3
3
 
4
4
  up:
5
- - ruby: 2.5.0
5
+ - ruby: 2.6.4
6
6
  - bundler
7
7
 
8
8
  commands:
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
@@ -2,6 +2,9 @@ require 'resolv'
2
2
 
3
3
  module RecordStore
4
4
  class Provider
5
+ class Error < StandardError; end
6
+ class UnparseableBodyError < Error; end
7
+
5
8
  class << self
6
9
  def provider_for(object)
7
10
  ns_server =
@@ -136,12 +139,13 @@ module RecordStore
136
139
  def retry_on_connection_errors(
137
140
  max_timeouts: 5,
138
141
  max_conn_resets: 5,
142
+ max_retries: 5,
139
143
  delay: 1,
140
144
  backoff_multiplier: 2,
141
145
  max_backoff: 10
142
146
  )
143
147
  waiter = BackoffWaiter.new(
144
- "Waiting to retry after a connection reset",
148
+ 'Waiting to retry after a connection reset',
145
149
  initial_delay: delay,
146
150
  multiplier: backoff_multiplier,
147
151
  max_delay: max_backoff,
@@ -150,11 +154,16 @@ module RecordStore
150
154
  loop do
151
155
  begin
152
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')
153
162
  rescue Net::OpenTimeout, Errno::ETIMEDOUT
154
163
  raise if max_timeouts <= 0
155
164
  max_timeouts -= 1
156
165
 
157
- $stderr.puts("Retrying after a connection timeout")
166
+ $stderr.puts('Retrying after a connection timeout')
158
167
  rescue Errno::ECONNRESET
159
168
  raise if max_conn_resets <= 0
160
169
  max_conn_resets -= 1
@@ -3,8 +3,6 @@ require_relative 'ns1/patch_api_header'
3
3
 
4
4
  module RecordStore
5
5
  class Provider::NS1 < Provider
6
- class Error < StandardError; end
7
-
8
6
  class ApiAnswer
9
7
  class << self
10
8
  def from_full_api_answer(type:, record_id:, answer:)
@@ -180,7 +178,7 @@ module RecordStore
180
178
  unless updated
181
179
  error = +'while trying to update a record, could not find answer with fqdn: '
182
180
  error << "#{record.fqdn}, type; #{record.type}, id: #{id}"
183
- raise Error, error
181
+ raise RecordStore::Provider::Error, error
184
182
  end
185
183
 
186
184
  client.modify_record(
@@ -1,46 +1,59 @@
1
+ require 'net/http'
1
2
  require 'ns1'
2
3
 
3
4
  module RecordStore
4
5
  class Provider::NS1 < Provider
5
- class Error < StandardError; end
6
-
7
6
  class Client < ::NS1::Client
8
7
  def initialize(api_key:)
9
8
  super(api_key)
10
9
  end
11
10
 
12
11
  def zones
13
- super
12
+ zones = super
13
+ raise_if_error!(zones)
14
+ zones
14
15
  end
15
16
 
16
17
  def zone(name)
17
- super(name)
18
+ zone = super(name)
19
+ raise_if_error!(zone)
20
+ zone
18
21
  end
19
22
 
20
23
  def record(zone:, fqdn:, type:, must_exist: false)
21
24
  result = super(zone, fqdn, type)
22
- raise(Error, result.to_s) if must_exist && result.is_a?(NS1::Response::Error)
25
+ raise_if_error!(result) if must_exist
23
26
  return nil if result.is_a?(NS1::Response::Error)
24
27
  result
25
28
  end
26
29
 
27
30
  def create_record(zone:, fqdn:, type:, params:)
28
31
  result = super(zone, fqdn, type, params)
29
- raise(Error, result.to_s) if result.is_a?(NS1::Response::Error)
32
+ raise_if_error!(result)
30
33
  nil
31
34
  end
32
35
 
33
36
  def modify_record(zone:, fqdn:, type:, params:)
34
37
  result = super(zone, fqdn, type, params)
35
- raise(Error, result.to_s) if result.is_a?(NS1::Response::Error)
38
+ raise_if_error!(result)
36
39
  nil
37
40
  end
38
41
 
39
42
  def delete_record(zone:, fqdn:, type:)
40
43
  result = super(zone, fqdn, type)
41
- raise(Error, result.to_s) if result.is_a?(NS1::Response::Error)
44
+ raise_if_error!(result)
42
45
  nil
43
46
  end
47
+
48
+ private
49
+
50
+ def raise_if_error!(result)
51
+ return unless result.is_a?(NS1::Response::Error)
52
+ if result.is_a?(NS1::Response::UnparsableBodyError)
53
+ raise RecordStore::Provider::UnparseableBodyError, result.to_s
54
+ end
55
+ raise RecordStore::Provider::Error, result.to_s
56
+ end
44
57
  end
45
58
  end
46
59
  end
@@ -1,6 +1,13 @@
1
1
  require 'net/http'
2
2
  require_relative '../provider_utils/waiter'
3
3
 
4
+ class NS1::Response::UnparsableBodyError < NS1::Response::Error
5
+ def initialize(status)
6
+ @status = status
7
+ super({}, status)
8
+ end
9
+ end
10
+
4
11
  # Patch the method which retrieves headers for API rate limit dynamically
5
12
  module NS1::Transport
6
13
  class NetHttp
@@ -18,15 +25,17 @@ module NS1::Transport
18
25
  rate_limit.wait(sleep_time)
19
26
  end
20
27
 
21
- body = JSON.parse(response.body)
22
- case response
23
- when Net::HTTPOK
24
- NS1::Response::Success.new(body, response.code.to_i)
25
- else
26
- NS1::Response::Error.new(body, response.code.to_i)
28
+ begin
29
+ body = JSON.parse(response.body)
30
+ case response
31
+ when Net::HTTPOK
32
+ NS1::Response::Success.new(body, response.code.to_i)
33
+ else
34
+ NS1::Response::Error.new(body, response.code.to_i)
35
+ end
36
+ rescue JSON::ParserError
37
+ NS1::Response::UnparsableBodyError.new(response.code.to_i)
27
38
  end
28
- rescue JSON::ParserError
29
- raise NS1::Transport::ResponseParseError
30
39
  end
31
40
  end
32
41
  end
@@ -5,7 +5,7 @@ class Waiter
5
5
 
6
6
  attr_accessor :message
7
7
 
8
- def wait(sleep_time)
8
+ def wait(sleep_time, message: @message)
9
9
  while sleep_time > 0
10
10
  wait_time = [10, sleep_time].min
11
11
  puts "#{message} (#{sleep_time}s left)" if wait_time > 1
@@ -34,8 +34,8 @@ class BackoffWaiter < Waiter
34
34
  @current_delay = @initial_delay
35
35
  end
36
36
 
37
- def wait
38
- super(@current_delay)
37
+ def wait(message: @message)
38
+ super(@current_delay, message: message)
39
39
  @current_delay = [@current_delay * @multiplier, @max_delay].compact.min
40
40
  end
41
41
  end
@@ -1,3 +1,3 @@
1
1
  module RecordStore
2
- VERSION = '6.2.1'.freeze
2
+ VERSION = '6.5.0'.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)
@@ -45,13 +46,18 @@ module RecordStore
45
46
  end
46
47
  end
47
48
 
48
- MAX_PARALLEL_THREADS = 10
49
+ DEFAULT_MAX_PARALLEL_THREADS = 10
50
+
51
+ def max_parallel_threads
52
+ (ENV['RECORD_STORE_MAX_THREADS'] || DEFAULT_MAX_PARALLEL_THREADS).to_i
53
+ end
54
+
49
55
  def modified(verbose: false) # rubocop:disable Lint/UnusedMethodArgument
50
56
  modified_zones = []
51
57
  mutex = Mutex.new
52
58
  zones = all
53
59
 
54
- (1..MAX_PARALLEL_THREADS).map do
60
+ (1..max_parallel_threads).map do
55
61
  Thread.new do
56
62
  current_zone = nil
57
63
  while zones.any?
@@ -65,10 +71,11 @@ module RecordStore
65
71
  end
66
72
  end
67
73
 
68
- def initialize(name:, records: [], config: {})
74
+ def initialize(name:, records: [], config: {}, abstract_syntax_trees: {})
69
75
  @name = Record.ensure_ends_with_dot(name)
70
76
  @config = RecordStore::Zone::Config.new(config.deep_symbolize_keys)
71
77
  @records = build_records(records)
78
+ @abstract_syntax_trees = abstract_syntax_trees
72
79
  end
73
80
 
74
81
  def build_changesets(all: false)
@@ -184,7 +191,13 @@ module RecordStore
184
191
  end
185
192
 
186
193
  def build_records(records)
187
- 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
188
201
  end
189
202
 
190
203
  def validate_records
@@ -267,11 +280,11 @@ module RecordStore
267
280
  suffix = wildcard[1..-1]
268
281
 
269
282
  terminal_records = records.map(&:fqdn)
270
- .select { |record| record.match?(/^([a-zA-Z0-9-_]+\.[a-zA-Z0-9-_])#{Regexp.escape(suffix)}$/) }
283
+ .select { |record| record.match?(/^([a-zA-Z0-9\-_]+\.[a-zA-Z0-9\-_])#{Regexp.escape(suffix)}$/) }
271
284
  next unless terminal_records.any?
272
285
 
273
286
  intermediate_records = records.map(&:fqdn)
274
- .select { |record| record.match?(/^([a-zA-Z0-9-_]+)#{Regexp.escape(suffix)}$/) }
287
+ .select { |record| record.match?(/^([a-zA-Z0-9\-_]+)#{Regexp.escape(suffix)}$/) }
275
288
  terminal_records.each do |terminal_record|
276
289
  non_terminal = terminal_record.partition('.').last
277
290
  errors.add(:records, "found empty non-terminal #{non_terminal} "\
@@ -296,5 +309,34 @@ module RecordStore
296
309
 
297
310
  errors.add(:records, "ALIAS record should be defined on the root of the zone: #{alias_record}")
298
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
299
341
  end
300
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,94 @@
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
+ filters_for_records_to_template = YAML.load(template_file).deep_symbolize_keys[:each_record]
30
+
31
+ new(template: ERB.new(template_file), filters_for_records_to_template: filters_for_records_to_template)
32
+ end
33
+
34
+ private
35
+
36
+ def template_filepath_for(filename:)
37
+ "#{RecordStore.implicit_records_templates_path}/#{filename}"
38
+ end
39
+ end
40
+
41
+ def initialize(template:, filters_for_records_to_template:)
42
+ @template = template
43
+ @filters_for_records_to_template = filters_for_records_to_template
44
+ end
45
+
46
+ def generate_records_to_inject(current_records:)
47
+ current_records
48
+ .select { |record| should_template?(record: record) }
49
+ .map { |record| template_record_for(record: record, current_records: current_records) }
50
+ .each_with_object([]) do |template_records, records_to_inject|
51
+ next unless should_inject?(
52
+ template_records: template_records,
53
+ current_records: current_records + records_to_inject
54
+ )
55
+
56
+ records_to_inject.push(
57
+ *template_records.fetch(:injected_records, []).map { |r_yml| Record.build_from_yaml_definition(r_yml) }
58
+ )
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ attr_reader :template, :filters_for_records_to_template
65
+
66
+ def should_inject?(template_records:, current_records:)
67
+ current_records.none? do |record|
68
+ template_records[:conflict_with].any? do |filter|
69
+ record_match?(record: record, filter: filter)
70
+ end
71
+ end
72
+ end
73
+
74
+ def should_template?(record:)
75
+ filters_for_records_to_template.any? { |filter| record_match?(record: record, filter: filter) }
76
+ end
77
+
78
+ def record_match?(record:, filter:)
79
+ filter.all? do |key, value|
80
+ record.public_send(key) == value
81
+ end
82
+ end
83
+
84
+ def template_record_for(record:, current_records:)
85
+ context = TemplateContext.build(record: record, current_records: current_records)
86
+
87
+ YAML.load(
88
+ template.result(context.fetch_binding)
89
+ ).deep_symbolize_keys
90
+ end
91
+ end
92
+ end
93
+ end
94
+ 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'
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,13 @@
1
+ each_record:
2
+ - type: A
3
+ fqdn: abc.123.com.
4
+ - type: TXT
5
+ conflict_with:
6
+ - type: TXT
7
+ fqdn: <%= record.fqdn %>.more.domain.com.
8
+ injected_records:
9
+ - type: CNAME
10
+ ttl: 3600
11
+ fqdn: <%= record.fqdn %>.added.information.com
12
+ cname: <%= record.fqdn %>.more.added.information.com.
13
+
@@ -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.2.1
4
+ version: 6.5.0
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-08-27 00:00:00.000000000 Z
12
+ date: 2021-06-04 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'
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'
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.17
418
421
  signing_key:
419
422
  specification_version: 4
420
423
  summary: Manage DNS using git