record_store 5.10.0 → 6.1.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: 305f971c2df40732e6ed0a53798af1ef1ffe4bda959454beea2c9f2d4da09ee7
4
- data.tar.gz: 4233420dd7d25ac624d52d643a4326762133f6531784de8174c1de15ca890a57
3
+ metadata.gz: f7a5da9483eaf8ce4eb82e8d974fefe82f7f481cde945a45ec6ae204b02524dc
4
+ data.tar.gz: 51e681688b6cc80c851f801ad00bb07ccf473ea2fb0b1c8a8e722d3cbc2ec196
5
5
  SHA512:
6
- metadata.gz: 99034b1c2625066dd7c4d32ee33582ef58ad2203197c52650f2a810365799604969b49575699f70ad8a009ee443838cd2de8989e75724dc4dc8967fecfbc6e63
7
- data.tar.gz: 150539a073dc355c83288f0356ba8db15d28287e17977dd7cce882487a439f19cfa614305cd67b3f1ffe1aa21f7359c170a168803248af9d8eb914f5fd844496
6
+ metadata.gz: 502acb53fcef150d5cb5442414c47552fc199efe66012e85d30745ae25c8af326064283c855ba12f4cead5b6f7b09f5a97c0b3bd6a4de1a010bba15664954335
7
+ data.tar.gz: 491ee9ff80ccb35ceb7ffbe70776948cd459467df8c213917c08ac6830e249585dbd993849a44e413bd6fc8e38589601eab27601cf3061c9b51abd09a94898a0
@@ -803,7 +803,7 @@ Layout/SpaceInsideRangeLiteral:
803
803
  Style/SymbolLiteral:
804
804
  Enabled: true
805
805
 
806
- Layout/Tab:
806
+ Layout/IndentationStyle:
807
807
  Enabled: true
808
808
 
809
809
  Layout/TrailingWhitespace:
@@ -1,5 +1,23 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 6.1.1
4
+ - Emit messages when waiting for rate-limit to elapse for DNSimple and NS1 providers, so deployment does not timeout [BUGFIX]
5
+
6
+ ## 6.1.0
7
+ - sort zone files [FEATURE]
8
+ - CLI support for specifying zones for validate_authority [FEATURE]
9
+ - retry failed lookup using another nameserver if unreachable [BUGFIX]
10
+ - ignore records other than NS in authority section [BUGFIX]
11
+
12
+ ## 6.0.1
13
+ - add API rate limiting to DNSimple provider [FEATURE]
14
+
15
+ ## 6.0.0
16
+ - add `--all` option for `record-store diff` to compare ignored records too [FEATURE]
17
+
18
+ ## 5.11.0
19
+ - support PTR record type [FEATURE]
20
+
3
21
  ## 5.10.0
4
22
  - add `record-store validate_authority` command to sanity check delegation [FEATURE]
5
23
  - fix handling of NXDOMAIN, etc. when fetching authoritative nameservers [BUGFIX]
@@ -19,6 +19,7 @@ require 'record_store/record/caa'
19
19
  require 'record_store/record/cname'
20
20
  require 'record_store/record/mx'
21
21
  require 'record_store/record/ns'
22
+ require 'record_store/record/ptr'
22
23
  require 'record_store/record/sshfp'
23
24
  require 'record_store/record/txt'
24
25
  require 'record_store/record/spf'
@@ -36,12 +36,15 @@ module RecordStore
36
36
 
37
37
  attr_reader :current_records, :desired_records, :removals, :additions, :updates, :provider, :zone
38
38
 
39
- def self.build_from(provider:, zone:)
39
+ def self.build_from(provider:, zone:, all: false)
40
40
  current_zone = provider.build_zone(zone_name: zone.unrooted_name, config: zone.config)
41
41
 
42
+ current_records = all ? current_zone.all : current_zone.records
43
+ desired_records = all ? zone.all : zone.records
44
+
42
45
  new(
43
- current_records: current_zone.records,
44
- desired_records: zone.records,
46
+ current_records: current_records,
47
+ desired_records: desired_records,
45
48
  provider: provider,
46
49
  zone: zone.unrooted_name
47
50
  )
@@ -59,13 +59,18 @@ module RecordStore
59
59
  end
60
60
  end
61
61
 
62
- desc 'diff', 'Displays the DNS differences between the zone files in this repo and production'
62
+ desc 'diff [ZONE ...]', 'Displays the DNS differences between the zone files in this repo and production'
63
+ option :all, desc: 'Include all records', aliases: '-a', type: :boolean, default: false
63
64
  option :verbose, desc: 'Print records that haven\'t diverged', aliases: '-v', type: :boolean, default: false
64
- def diff
65
- puts "Diffing #{Zone.defined.count} zones"
65
+ def diff(*zones)
66
+ puts "Diffing #{zones.any? ? zones.count : Zone.defined.count} zone(s)"
67
+
68
+ all = options.fetch('all')
66
69
 
67
70
  Zone.each do |name, zone|
68
- changesets = zone.build_changesets
71
+ next unless zones.empty? || zones.include?(name)
72
+
73
+ changesets = zone.build_changesets(all: all)
69
74
 
70
75
  if !options.fetch('verbose') && changesets.all?(&:empty?)
71
76
  print_and_flush('.')
@@ -81,21 +86,21 @@ module RecordStore
81
86
  puts '-' * 20
82
87
  puts "Provider: #{changeset.provider}"
83
88
 
84
- if !changeset.additions.empty? || options.fetch('verbose')
89
+ if changeset.additions.any?
85
90
  puts "Add:"
86
91
  changeset.additions.map(&:record).each do |record|
87
92
  puts " - #{record}"
88
93
  end
89
94
  end
90
95
 
91
- if !changeset.removals.empty? || options.fetch('verbose')
96
+ if changeset.removals.any?
92
97
  puts "Remove:"
93
98
  changeset.removals.map(&:record).each do |record|
94
99
  puts " - #{record}"
95
100
  end
96
101
  end
97
102
 
98
- if !changeset.updates.empty? || options.fetch('verbose')
103
+ if changeset.updates.any?
99
104
  puts "Update:"
100
105
  changeset.updates.map(&:record).each do |record|
101
106
  puts " - #{record}"
@@ -215,12 +220,14 @@ module RecordStore
215
220
  end
216
221
  end
217
222
 
218
- desc 'validate_authority', 'Validates that authoritative nameservers match the providers'
223
+ desc 'validate_authority [ZONE ...]', 'Validates that authoritative nameservers match the providers'
219
224
  option :verbose, desc: 'Include valid zones in output', aliases: '-v', type: :boolean, default: false
220
- def validate_authority
225
+ def validate_authority(*zones)
221
226
  verbose = options.fetch('verbose')
222
227
 
223
228
  Zone.each do |name, zone|
229
+ next unless zones.empty? || zones.include?(name)
230
+
224
231
  authority = zone.fetch_authority
225
232
 
226
233
  delegation = Hash.new { |h, k| h[k] = [] }
@@ -1,10 +1,11 @@
1
1
  require 'dnsimple'
2
+ require_relative 'dnsimple/patch_api_header'
2
3
 
3
4
  module RecordStore
4
5
  class Provider::DNSimple < Provider
5
6
  class << self
6
7
  def record_types
7
- super | Set.new(['SSHFP'])
8
+ super | Set.new(%w(PTR SSHFP))
8
9
  end
9
10
 
10
11
  def supports_alias?
@@ -99,6 +100,8 @@ module RecordStore
99
100
  record.merge!(preference: api_record.priority, exchange: api_record.content)
100
101
  when 'NS'
101
102
  record.merge!(nsdname: api_record.content)
103
+ when 'PTR'
104
+ record.merge!(ptrdname: api_record.content)
102
105
  when 'SSHFP'
103
106
  algorithm, fptype, fingerprint = api_record.content.split(' ')
104
107
  record.merge!(
@@ -143,6 +146,8 @@ module RecordStore
143
146
  record_hash[:content] = record.exchange.chomp('.')
144
147
  when 'NS'
145
148
  record_hash[:content] = record.nsdname.chomp('.')
149
+ when 'PTR'
150
+ record_hash[:content] = record.ptrdname.chomp('.')
146
151
  when 'SSHFP'
147
152
  record_hash[:content] = record.rdata_txt
148
153
  when 'SPF', 'TXT'
@@ -0,0 +1,33 @@
1
+ require_relative '../provider_utils/rate_limit'
2
+
3
+ # Patch Dnsimple client method which retrieves headers for API rate limit dynamically
4
+ module Dnsimple
5
+ class Client
6
+ def execute(method, path, data = nil, options = {})
7
+ response = request(method, path, data, options)
8
+ rate_limit_sleep(response.headers['x-ratelimit-reset'].to_i, response.headers['x-ratelimit-remaining'].to_i)
9
+
10
+ case response.code
11
+ when 200..299
12
+ response
13
+ when 401
14
+ raise AuthenticationFailed, response['message']
15
+ when 404
16
+ raise NotFoundError, response
17
+ else
18
+ raise RequestError, response
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def rate_limit_sleep(rate_limit_reset, rate_limit_remaining)
25
+ rate_limit_reset_in = [0, rate_limit_reset - Time.now.to_i].max
26
+ rate_limit_periods = rate_limit_remaining + 1
27
+ sleep_time = rate_limit_reset_in / rate_limit_periods.to_f
28
+
29
+ rate_limit = RateLimit.new('DNSimple')
30
+ rate_limit.sleep_for(sleep_time)
31
+ end
32
+ end
33
+ end
@@ -41,6 +41,10 @@ module RecordStore
41
41
  end
42
42
 
43
43
  class << self
44
+ def record_types
45
+ super | Set.new(%w(PTR))
46
+ end
47
+
44
48
  def client
45
49
  Provider::NS1::Client.new(api_key: secrets['api_key'])
46
50
  end
@@ -229,6 +233,8 @@ module RecordStore
229
233
  )
230
234
  when 'NS'
231
235
  record.merge!(nsdname: answer.rrdata_string)
236
+ when 'PTR'
237
+ record.merge!(ptrdname: answer.rrdata_string)
232
238
  when 'SPF', 'TXT'
233
239
  record.merge!(txtdata: answer.rrdata_string.gsub(';', '\;'))
234
240
  when 'SRV'
@@ -1,11 +1,15 @@
1
1
  require 'net/http'
2
+ require_relative '../provider_utils/rate_limit'
2
3
 
3
4
  # Patch the method which retrieves headers for API rate limit dynamically
4
5
  module NS1::Transport
5
6
  class NetHttp
6
7
  def process_response(response)
7
- sleep(response.to_hash["x-ratelimit-period"].first.to_i /
8
- [1, response.to_hash["x-ratelimit-remaining"].first.to_i].max.to_f)
8
+ sleep_time = response.to_hash['x-ratelimit-period'].first.to_i /
9
+ [1, response.to_hash['x-ratelimit-remaining'].first.to_i].max.to_f
10
+
11
+ rate_limit = RateLimit.new('NS1')
12
+ rate_limit.sleep_for(sleep_time)
9
13
 
10
14
  body = JSON.parse(response.body)
11
15
  case response
@@ -0,0 +1,14 @@
1
+ class RateLimit
2
+ def initialize(provider)
3
+ @provider = provider
4
+ end
5
+
6
+ def sleep_for(sleep_time)
7
+ while sleep_time > 0
8
+ wait = [10, sleep_time].min
9
+ puts "Waiting on #{@provider} rate-limit" if wait > 1
10
+ sleep(wait)
11
+ sleep_time -= wait
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,42 @@
1
+ module RecordStore
2
+ class Record::PTR < Record
3
+ attr_accessor :ptrdname
4
+
5
+ OCTET_LABEL_SEQUENCE_REGEX = /\A(?:([0-9]|[1-9][0-9]|[1-9][0-9][0-9])\.){1,4}/
6
+ IN_ADDR_ARPA_SUFFIX_REGEX = /in-addr\.arpa\.\z/
7
+ FQDN_FORMAT_REGEX = Regexp.new(OCTET_LABEL_SEQUENCE_REGEX.source + IN_ADDR_ARPA_SUFFIX_REGEX.source)
8
+
9
+ validates_format_of :fqdn, with: FQDN_FORMAT_REGEX
10
+
11
+ validate :validate_fqdn_octets_in_range
12
+ validate :validate_fqdn_is_in_addr_arpa_subzone
13
+
14
+ def initialize(record)
15
+ super
16
+
17
+ @ptrdname = Record.ensure_ends_with_dot(record.fetch(:ptrdname))
18
+ end
19
+
20
+ def rdata
21
+ { ptrdname: ptrdname }
22
+ end
23
+
24
+ def rdata_txt
25
+ ptrdname.to_s
26
+ end
27
+
28
+ def validate_fqdn_octets_in_range
29
+ OCTET_LABEL_SEQUENCE_REGEX.match(fqdn) do |m|
30
+ unless m.captures.all? { |o| o.to_d.between?(0, 255) }
31
+ errors.add(:fqdn, 'octet labels must be within the range 0-255')
32
+ end
33
+ end
34
+ end
35
+
36
+ def validate_fqdn_is_in_addr_arpa_subzone
37
+ unless IN_ADDR_ARPA_SUFFIX_REGEX.match?(fqdn)
38
+ errors.add(:fqdn, 'PTR records may only exist in the in-addr.arpa zone')
39
+ end
40
+ end
41
+ end
42
+ end
@@ -1,3 +1,3 @@
1
1
  module RecordStore
2
- VERSION = '5.10.0'.freeze
2
+ VERSION = '6.1.1'.freeze
3
3
  end
@@ -70,10 +70,10 @@ module RecordStore
70
70
  @records = build_records(records)
71
71
  end
72
72
 
73
- def build_changesets
73
+ def build_changesets(all: false)
74
74
  @changesets ||= begin
75
75
  providers.map do |provider|
76
- Changeset.build_from(provider: provider, zone: self)
76
+ Changeset.build_from(provider: provider, zone: self, all: all)
77
77
  end
78
78
  end
79
79
  end
@@ -129,14 +129,12 @@ module RecordStore
129
129
  )
130
130
 
131
131
  def fetch_authority(nameserver = ROOT_SERVERS.sample)
132
- authority = Resolv::DNS.open(nameserver: nameserver) do |resolv|
133
- resolv.fetch_resource(name, Resolv::DNS::Resource::IN::SOA) do |reply, name|
134
- break if reply.answer.any?
132
+ authority = fetch_soa(nameserver) do |reply, _name|
133
+ break if reply.answer.any?
135
134
 
136
- raise "No authority found (#{name})" unless reply.authority.any?
135
+ raise "No authority found (#{name})" unless reply.authority.any?
137
136
 
138
- break extract_authority(reply)
139
- end
137
+ break extract_authority(reply.authority)
140
138
  end
141
139
 
142
140
  # candidate DNS name is returned instead when NXDomain or other error
@@ -147,18 +145,39 @@ module RecordStore
147
145
 
148
146
  private
149
147
 
150
- def extract_authority(reply)
151
- authority = reply.authority.sample
148
+ def fetch_soa(nameserver)
149
+ Resolv::DNS.open(nameserver: nameserver) do |resolv|
150
+ resolv.fetch_resource(name, Resolv::DNS::Resource::IN::SOA) do |reply, name|
151
+ yield reply, name
152
+ end
153
+ end
154
+ end
152
155
 
153
- if unrooted_name.casecmp?(authority.first.to_s)
154
- build_authority(reply.authority)
156
+ def resolve_authority(authority)
157
+ nameservers = authority.map { |a| a.last.name.to_s }
158
+
159
+ begin
160
+ nameserver = nameservers.shift
161
+ fetch_authority(nameserver)
162
+ rescue Errno::EHOSTUNREACH => e
163
+ $stderr.puts "Warning: #{e} [host=#{nameserver}]"
164
+ raise if nameservers.empty?
165
+ retry
166
+ end
167
+ end
168
+
169
+ def extract_authority(authority)
170
+ if unrooted_name.casecmp?(authority.first.first.to_s)
171
+ build_authority(authority)
155
172
  else
156
- fetch_authority(authority.last.name.to_s) || build_authority(reply.authority)
173
+ resolve_authority(authority) || build_authority(authority)
157
174
  end
158
175
  end
159
176
 
160
177
  def build_authority(authority)
161
- authority.map.with_index do |(name, ttl, data), index|
178
+ ns = authority.select { |_name, _ttl, data| data.is_a?(Resolv::DNS::Resource::IN::NS) }
179
+
180
+ ns.map.with_index do |(name, ttl, data), index|
162
181
  Record::NS.new(ttl: ttl, fqdn: name.to_s, nsdname: data.name.to_s, record_id: index)
163
182
  end
164
183
  end
@@ -10,6 +10,7 @@ module RecordStore
10
10
  def defined
11
11
  @defined ||= yaml_files
12
12
  .map { |file| load_yml_zone_definition(file) }
13
+ .sort_by(&:unrooted_name)
13
14
  .index_by(&:unrooted_name)
14
15
  end
15
16
 
@@ -36,7 +36,7 @@ Gem::Specification.new do |spec|
36
36
  spec.add_runtime_dependency 'fog-xml'
37
37
  spec.add_runtime_dependency 'fog-dynect', '~> 0.4.0'
38
38
  spec.add_runtime_dependency 'dnsimple', '~> 4.4.0'
39
- spec.add_runtime_dependency 'google-cloud-dns'
39
+ spec.add_runtime_dependency 'google-cloud-dns', '~> 0.31.0'
40
40
  spec.add_runtime_dependency 'ruby-limiter', '~> 1.0', '>= 1.0.1'
41
41
  spec.add_runtime_dependency 'ns1'
42
42
  spec.add_runtime_dependency 'oci', '~> 2.6.0'
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: 5.10.0
4
+ version: 6.1.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-04-15 00:00:00.000000000 Z
12
+ date: 2020-06-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: thor
@@ -141,16 +141,16 @@ dependencies:
141
141
  name: google-cloud-dns
142
142
  requirement: !ruby/object:Gem::Requirement
143
143
  requirements:
144
- - - ">="
144
+ - - "~>"
145
145
  - !ruby/object:Gem::Version
146
- version: '0'
146
+ version: 0.31.0
147
147
  type: :runtime
148
148
  prerelease: false
149
149
  version_requirements: !ruby/object:Gem::Requirement
150
150
  requirements:
151
- - - ">="
151
+ - - "~>"
152
152
  - !ruby/object:Gem::Version
153
- version: '0'
153
+ version: 0.31.0
154
154
  - !ruby/object:Gem::Dependency
155
155
  name: ruby-limiter
156
156
  requirement: !ruby/object:Gem::Requirement
@@ -355,12 +355,14 @@ files:
355
355
  - lib/record_store/cli.rb
356
356
  - lib/record_store/provider.rb
357
357
  - lib/record_store/provider/dnsimple.rb
358
+ - lib/record_store/provider/dnsimple/patch_api_header.rb
358
359
  - lib/record_store/provider/dynect.rb
359
360
  - lib/record_store/provider/google_cloud_dns.rb
360
361
  - lib/record_store/provider/ns1.rb
361
362
  - lib/record_store/provider/ns1/client.rb
362
363
  - lib/record_store/provider/ns1/patch_api_header.rb
363
364
  - lib/record_store/provider/oracle_cloud_dns.rb
365
+ - lib/record_store/provider/provider_utils/rate_limit.rb
364
366
  - lib/record_store/record.rb
365
367
  - lib/record_store/record/a.rb
366
368
  - lib/record_store/record/aaaa.rb
@@ -369,6 +371,7 @@ files:
369
371
  - lib/record_store/record/cname.rb
370
372
  - lib/record_store/record/mx.rb
371
373
  - lib/record_store/record/ns.rb
374
+ - lib/record_store/record/ptr.rb
372
375
  - lib/record_store/record/spf.rb
373
376
  - lib/record_store/record/srv.rb
374
377
  - lib/record_store/record/sshfp.rb