record_store 6.2.1 → 6.5.0

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