record_store 5.11.0 → 6.1.2
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 +4 -4
- data/.rubocop-https---shopify-github-io-ruby-style-guide-rubocop-yml +2 -2
- data/CHANGELOG.md +15 -0
- data/lib/record_store/changeset.rb +6 -3
- data/lib/record_store/cli.rb +16 -9
- data/lib/record_store/provider.rb +30 -0
- data/lib/record_store/provider/dnsimple.rb +14 -9
- data/lib/record_store/provider/dnsimple/patch_api_header.rb +33 -0
- data/lib/record_store/provider/ns1.rb +6 -2
- data/lib/record_store/provider/ns1/patch_api_header.rb +13 -2
- data/lib/record_store/provider/provider_utils/rate_limit.rb +14 -0
- data/lib/record_store/version.rb +1 -1
- data/lib/record_store/zone.rb +33 -14
- data/lib/record_store/zone/yaml_definitions.rb +1 -0
- data/record_store.gemspec +1 -1
- metadata +9 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4d05ecb06f048975b797baa10df042222f03ee4f0b1b5e268afa87e97b16ede6
|
4
|
+
data.tar.gz: 4ca96a597c88f5d17df9f85ea50e8743b0cab6fa4d57196447d3808cc22711a6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 98581b53f61499cb2f83d92ae2de4cf132ce2784cf56f8dc81531228f4090995f8e7a5a2c70f0a8fcac0cc5a3bdea29dd5c1d7b6996eef61e416c0c65c6a252d
|
7
|
+
data.tar.gz: 8e753593bc358e83f82a0191f07528e14aa793305570afae5fcc865e1bdd97fffb48319fec43642e662c7b62e1070dd3b384464b9ca55a39e2a590fcf0f6738b
|
@@ -671,7 +671,7 @@ Style/LineEndConcatenation:
|
|
671
671
|
Style/MethodCallWithoutArgsParentheses:
|
672
672
|
Enabled: true
|
673
673
|
|
674
|
-
|
674
|
+
Lint/MissingSuper:
|
675
675
|
Enabled: true
|
676
676
|
|
677
677
|
Style/MissingRespondToMissing:
|
@@ -964,7 +964,7 @@ Lint/UselessAccessModifier:
|
|
964
964
|
Lint/UselessAssignment:
|
965
965
|
Enabled: true
|
966
966
|
|
967
|
-
Lint/
|
967
|
+
Lint/BinaryOperatorWithIdenticalOperands:
|
968
968
|
Enabled: true
|
969
969
|
|
970
970
|
Lint/UselessElseWithoutRescue:
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,20 @@
|
|
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
|
+
|
3
18
|
## 5.11.0
|
4
19
|
- support PTR record type [FEATURE]
|
5
20
|
|
@@ -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:
|
44
|
-
desired_records:
|
46
|
+
current_records: current_records,
|
47
|
+
desired_records: desired_records,
|
45
48
|
provider: provider,
|
46
49
|
zone: zone.unrooted_name
|
47
50
|
)
|
data/lib/record_store/cli.rb
CHANGED
@@ -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}
|
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
|
-
|
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
|
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
|
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
|
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] = [] }
|
@@ -128,6 +128,36 @@ module RecordStore
|
|
128
128
|
|
129
129
|
dns.getresource(zone_name, Resolv::DNS::Resource::IN::SOA).mname.to_s
|
130
130
|
end
|
131
|
+
|
132
|
+
def retry_on_connection_errors(
|
133
|
+
max_timeouts: 5,
|
134
|
+
max_conn_resets: 5,
|
135
|
+
delay: 1,
|
136
|
+
backoff_multiplier: 2,
|
137
|
+
max_backoff: 10
|
138
|
+
)
|
139
|
+
loop do
|
140
|
+
begin
|
141
|
+
return yield
|
142
|
+
rescue Net::OpenTimeout
|
143
|
+
raise if max_timeouts <= 0
|
144
|
+
max_timeouts -= 1
|
145
|
+
|
146
|
+
$stderr.puts("Retrying after a connection timeout")
|
147
|
+
rescue Errno::ECONNRESET
|
148
|
+
raise if max_conn_resets <= 0
|
149
|
+
max_conn_resets -= 1
|
150
|
+
|
151
|
+
$stderr.puts("Retrying in #{delay}s after a connection reset")
|
152
|
+
backoff_sleep(delay)
|
153
|
+
delay = [delay * backoff_multiplier, max_backoff].min
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def backoff_sleep(delay)
|
159
|
+
sleep(delay)
|
160
|
+
end
|
131
161
|
end
|
132
162
|
end
|
133
163
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'dnsimple'
|
2
|
+
require_relative 'dnsimple/patch_api_header'
|
2
3
|
|
3
4
|
module RecordStore
|
4
5
|
class Provider::DNSimple < Provider
|
@@ -13,19 +14,23 @@ module RecordStore
|
|
13
14
|
|
14
15
|
# returns an array of Record objects that match the records which exist in the provider
|
15
16
|
def retrieve_current_records(zone:, stdout: $stdout)
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
17
|
+
retry_on_connection_errors do
|
18
|
+
session.zones.all_records(account_id, zone).data.map do |record|
|
19
|
+
begin
|
20
|
+
build_from_api(record, zone)
|
21
|
+
rescue StandardError
|
22
|
+
stdout.puts "Cannot build record: #{record}"
|
23
|
+
raise
|
24
|
+
end
|
25
|
+
end.compact
|
26
|
+
end
|
24
27
|
end
|
25
28
|
|
26
29
|
# Returns an array of the zones managed by provider as strings
|
27
30
|
def zones
|
28
|
-
|
31
|
+
retry_on_connection_errors do
|
32
|
+
session.zones.all_zones(account_id).data.map(&:name)
|
33
|
+
end
|
29
34
|
end
|
30
35
|
|
31
36
|
private
|
@@ -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
|
@@ -60,14 +60,18 @@ module RecordStore
|
|
60
60
|
|
61
61
|
# Returns an array of the zones managed by provider as strings
|
62
62
|
def zones
|
63
|
-
|
63
|
+
retry_on_connection_errors do
|
64
|
+
client.zones.map { |zone| zone['zone'] }
|
65
|
+
end
|
64
66
|
end
|
65
67
|
|
66
68
|
private
|
67
69
|
|
68
70
|
# Fetches simplified records for the provided zone
|
69
71
|
def records_for_zone(zone)
|
70
|
-
|
72
|
+
retry_on_connection_errors do
|
73
|
+
client.zone(zone)['records']
|
74
|
+
end
|
71
75
|
end
|
72
76
|
|
73
77
|
# Creates a new record to the zone. It is expected this call modifies external state.
|
@@ -1,11 +1,22 @@
|
|
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
|
7
|
+
X_RATELIMIT_PERIOD = 'x-ratelimit-period'.freeze
|
8
|
+
X_RATELIMIT_REMAINING = 'x-ratelimit-remaining'.freeze
|
9
|
+
|
6
10
|
def process_response(response)
|
7
|
-
|
8
|
-
|
11
|
+
response_hash = response.to_hash
|
12
|
+
|
13
|
+
if response_hash.key?(X_RATELIMIT_PERIOD) && response_hash.key?(X_RATELIMIT_REMAINING)
|
14
|
+
sleep_time = response_hash[X_RATELIMIT_PERIOD].first.to_i /
|
15
|
+
[1, response_hash[X_RATELIMIT_REMAINING].first.to_i].max.to_f
|
16
|
+
|
17
|
+
rate_limit = RateLimit.new('NS1')
|
18
|
+
rate_limit.sleep_for(sleep_time)
|
19
|
+
end
|
9
20
|
|
10
21
|
body = JSON.parse(response.body)
|
11
22
|
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
|
data/lib/record_store/version.rb
CHANGED
data/lib/record_store/zone.rb
CHANGED
@@ -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 =
|
133
|
-
|
134
|
-
break if reply.answer.any?
|
132
|
+
authority = fetch_soa(nameserver) do |reply, _name|
|
133
|
+
break if reply.answer.any?
|
135
134
|
|
136
|
-
|
135
|
+
raise "No authority found (#{name})" unless reply.authority.any?
|
137
136
|
|
138
|
-
|
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
|
151
|
-
|
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
|
-
|
154
|
-
|
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
|
-
|
173
|
+
resolve_authority(authority) || build_authority(authority)
|
157
174
|
end
|
158
175
|
end
|
159
176
|
|
160
177
|
def build_authority(authority)
|
161
|
-
authority.
|
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
|
data/record_store.gemspec
CHANGED
@@ -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:
|
4
|
+
version: 6.1.2
|
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-
|
12
|
+
date: 2020-08-18 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:
|
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:
|
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
|
@@ -412,7 +414,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
412
414
|
- !ruby/object:Gem::Version
|
413
415
|
version: '0'
|
414
416
|
requirements: []
|
415
|
-
rubygems_version: 3.0.
|
417
|
+
rubygems_version: 3.0.3
|
416
418
|
signing_key:
|
417
419
|
specification_version: 4
|
418
420
|
summary: Manage DNS using git
|