record_store 6.6.0 → 6.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/dependabot.yml +17 -0
- data/.github/workflows/ci.yml +1 -1
- data/.github/workflows/stale-action-handling.yml +31 -0
- data/CHANGELOG.md +3 -0
- data/README.md +6 -0
- data/bin/rubocop +4 -2
- data/lib/record_store/changeset.rb +22 -18
- data/lib/record_store/cli.rb +6 -3
- data/lib/record_store/provider/dnsimple.rb +4 -4
- data/lib/record_store/provider/dynect.rb +2 -2
- data/lib/record_store/provider/ns1/client.rb +3 -1
- data/lib/record_store/provider/ns1/patch_api_header.rb +1 -1
- data/lib/record_store/provider/ns1.rb +7 -7
- data/lib/record_store/provider/oracle_cloud_dns.rb +4 -3
- data/lib/record_store/provider.rb +6 -1
- data/lib/record_store/record/alias.rb +1 -1
- data/lib/record_store/record/caa.rb +3 -1
- data/lib/record_store/record/cname.rb +1 -1
- data/lib/record_store/record.rb +6 -4
- data/lib/record_store/version.rb +1 -1
- data/lib/record_store/zone/config/implicit_record_template.rb +12 -7
- data/lib/record_store/zone/yaml_definitions.rb +2 -0
- data/lib/record_store/zone.rb +11 -10
- data/lib/record_store.rb +8 -4
- data/record_store.gemspec +12 -12
- metadata +82 -62
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 02e832c94076da4058146c7bfad4d822c475ded9dd1bd33a1fe30fd8d3089d8a
|
4
|
+
data.tar.gz: 2488528b4db3555c5b3885ff12896e5a2079e7f3252ec6a333f16bfebc7fab04
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6d36d6ca7aefdcc839f454ccfb5c90ad92ed6998a9034065a0655aed66d6c3a04bde138d11d2852ae56799f8a1ffaa329b87024b7bcab10cd95b724278f24495
|
7
|
+
data.tar.gz: a7b3c47a1d85221509f7a3a75d863086ee53c7f400d4508e679d7c683d5f05c42f8636a9bc145797992137995a38213682866e149d8007eccbf0e28aea5fbaeb
|
@@ -0,0 +1,17 @@
|
|
1
|
+
version: 2
|
2
|
+
|
3
|
+
updates:
|
4
|
+
- package-ecosystem: bundler
|
5
|
+
directory: "/"
|
6
|
+
schedule:
|
7
|
+
interval: weekly
|
8
|
+
open-pull-requests-limit: 100
|
9
|
+
groups:
|
10
|
+
minor_versions:
|
11
|
+
update-types:
|
12
|
+
- 'minor'
|
13
|
+
- 'patch'
|
14
|
+
- package-ecosystem: github-actions
|
15
|
+
directory: '/'
|
16
|
+
schedule:
|
17
|
+
interval: weekly
|
data/.github/workflows/ci.yml
CHANGED
@@ -14,7 +14,7 @@ jobs:
|
|
14
14
|
ruby: [ 2.7.1, 3.2.1 ]
|
15
15
|
name: Test Ruby ${{ matrix.ruby }}
|
16
16
|
steps:
|
17
|
-
- uses: actions/checkout@
|
17
|
+
- uses: actions/checkout@v4
|
18
18
|
- name: Install dependencies
|
19
19
|
run: |
|
20
20
|
sudo apt-get update && sudo apt-get install build-essential libcurl4-openssl-dev
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# Configuration for stale action https://github.com/actions/stale
|
2
|
+
name: 'Close stale issues and PRs'
|
3
|
+
on:
|
4
|
+
schedule:
|
5
|
+
- cron: '0 14 * * *'
|
6
|
+
|
7
|
+
jobs:
|
8
|
+
stale:
|
9
|
+
runs-on: ubuntu-latest
|
10
|
+
|
11
|
+
permissions:
|
12
|
+
issues: write
|
13
|
+
pull-requests: write
|
14
|
+
|
15
|
+
steps:
|
16
|
+
- uses: actions/stale@v9
|
17
|
+
with:
|
18
|
+
ascending: true
|
19
|
+
operations-per-run: 100
|
20
|
+
stale-pr-message: 'As of today this PR is stale. If you want to keep it apply an update otherwise it will be closed in 7 days.'
|
21
|
+
close-pr-message: 'PR was closed because of missing activity.'
|
22
|
+
days-before-pr-stale: 90
|
23
|
+
days-before-pr-close: 7
|
24
|
+
stale-issue-message: >
|
25
|
+
This issue is stale because it has been open for 90 days with no activity. It will be closed if no further action occurs in 7 days.
|
26
|
+
close-issue-message: |
|
27
|
+
We are closing this issue because it has been inactive for a few months.
|
28
|
+
This probably means that it is not reproducible or it has been fixed in a newer version.
|
29
|
+
If it's an enhancement and hasn't been taken on since it was submitted, then it seems other issues have taken priority.
|
30
|
+
days-before-issue-stale: 90
|
31
|
+
days-before-issue-close: 7
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -20,6 +20,12 @@ record-store validate_initial_state # Validates state hasn't diverged since t
|
|
20
20
|
record-store validate_records # Validates that all DNS records have valid definitions
|
21
21
|
```
|
22
22
|
|
23
|
+
## Releasing a new version
|
24
|
+
* Bump the version [here](https://github.com/Shopify/record_store/blob/main/lib/record_store/version.rb).
|
25
|
+
* Add to CHANGELOG [here](https://github.com/Shopify/record_store/blob/main/CHANGELOG.md).
|
26
|
+
* PR, merge and shipit [here](https://shipit.shopify.io/shopify/record_store/production).
|
27
|
+
|
28
|
+
|
23
29
|
## Providers
|
24
30
|
|
25
31
|
Below is the list of DNS providers supported by Record Store. PRs [adding more](#adding-new-providers) are welcome.
|
data/bin/rubocop
CHANGED
@@ -9,8 +9,10 @@
|
|
9
9
|
#
|
10
10
|
|
11
11
|
require "pathname"
|
12
|
-
ENV["BUNDLE_GEMFILE"] ||= File.expand_path(
|
13
|
-
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path(
|
13
|
+
"../../Gemfile",
|
14
|
+
Pathname.new(__FILE__).realpath,
|
15
|
+
)
|
14
16
|
|
15
17
|
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
18
|
|
@@ -9,16 +9,18 @@ module RecordStore
|
|
9
9
|
@id = id
|
10
10
|
end
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
class << self
|
13
|
+
def addition(record)
|
14
|
+
new(type: :addition, record: record)
|
15
|
+
end
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
|
17
|
+
def removal(record)
|
18
|
+
new(type: :removal, record: record)
|
19
|
+
end
|
19
20
|
|
20
|
-
|
21
|
-
|
21
|
+
def update(id, record)
|
22
|
+
new(type: :update, record: record, id: id)
|
23
|
+
end
|
22
24
|
end
|
23
25
|
|
24
26
|
def removal?
|
@@ -36,18 +38,20 @@ module RecordStore
|
|
36
38
|
|
37
39
|
attr_reader :current_records, :desired_records, :removals, :additions, :updates, :provider, :zone
|
38
40
|
|
39
|
-
|
40
|
-
|
41
|
+
class << self
|
42
|
+
def build_from(provider:, zone:, all: false)
|
43
|
+
current_zone = provider.build_zone(zone_name: zone.unrooted_name, config: zone.config)
|
41
44
|
|
42
|
-
|
43
|
-
|
45
|
+
current_records = all ? current_zone.all : current_zone.records
|
46
|
+
desired_records = all ? zone.all : zone.records
|
44
47
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
48
|
+
new(
|
49
|
+
current_records: current_records,
|
50
|
+
desired_records: desired_records,
|
51
|
+
provider: provider,
|
52
|
+
zone: zone.unrooted_name,
|
53
|
+
)
|
54
|
+
end
|
51
55
|
end
|
52
56
|
|
53
57
|
def initialize(current_records: [], desired_records: [], provider:, zone:)
|
data/lib/record_store/cli.rb
CHANGED
@@ -10,8 +10,10 @@ module RecordStore
|
|
10
10
|
RecordStore.config_path = options.fetch('config', "#{Dir.pwd}/config.yml")
|
11
11
|
end
|
12
12
|
|
13
|
-
|
14
|
-
|
13
|
+
class << self
|
14
|
+
def exit_on_failure?
|
15
|
+
true
|
16
|
+
end
|
15
17
|
end
|
16
18
|
|
17
19
|
desc 'thaw', 'Thaws all zones under management to allow manual edits'
|
@@ -108,6 +110,7 @@ module RecordStore
|
|
108
110
|
end
|
109
111
|
|
110
112
|
next unless options.fetch('verbose')
|
113
|
+
|
111
114
|
puts "Unchanged:"
|
112
115
|
changeset.unchanged.each do |record|
|
113
116
|
puts " - #{record}"
|
@@ -147,7 +150,7 @@ module RecordStore
|
|
147
150
|
option :name, desc: 'Zone to download', aliases: '-n', type: :string, required: true
|
148
151
|
option :provider, desc: 'Provider in which this zone exists', aliases: '-p', type: :string
|
149
152
|
desc 'download', 'Downloads all records from zone and creates YAML zone definition in zones/ '\
|
150
|
-
|
153
|
+
'e.g. record-store download --name=shopify.io'
|
151
154
|
def download
|
152
155
|
name = options.fetch('name')
|
153
156
|
abort('Please omit the period at the end of the zone') if name.ends_with?('.')
|
@@ -20,7 +20,7 @@ module RecordStore
|
|
20
20
|
# returns an array of Record objects that match the records which exist in the provider
|
21
21
|
def retrieve_current_records(zone:, stdout: $stdout)
|
22
22
|
retry_on_connection_errors do
|
23
|
-
session.zones.
|
23
|
+
session.zones.all_zone_records(account_id, zone).data.map do |record|
|
24
24
|
build_from_api(record, zone)
|
25
25
|
rescue StandardError
|
26
26
|
stdout.puts "Cannot build record: #{record}"
|
@@ -40,7 +40,7 @@ module RecordStore
|
|
40
40
|
|
41
41
|
def add(record, zone)
|
42
42
|
record_hash = api_hash(record, zone)
|
43
|
-
res = session.zones.
|
43
|
+
res = session.zones.create_zone_record(account_id, zone, record_hash)
|
44
44
|
|
45
45
|
if record.type == 'ALIAS'
|
46
46
|
txt_alias = retrieve_current_records(zone: zone).detect do |rr|
|
@@ -53,7 +53,7 @@ module RecordStore
|
|
53
53
|
end
|
54
54
|
|
55
55
|
def remove(record, zone)
|
56
|
-
session.zones.
|
56
|
+
session.zones.delete_zone_record(account_id, zone, record.id)
|
57
57
|
end
|
58
58
|
|
59
59
|
def update(id, record, zone)
|
@@ -63,7 +63,7 @@ module RecordStore
|
|
63
63
|
def session
|
64
64
|
@dns ||= Dnsimple::Client.new(
|
65
65
|
base_url: secrets.fetch('base_url'),
|
66
|
-
access_token: secrets.fetch('api_token')
|
66
|
+
access_token: secrets.fetch('api_token'),
|
67
67
|
)
|
68
68
|
end
|
69
69
|
|
@@ -1,8 +1,8 @@
|
|
1
1
|
require 'fog/dynect'
|
2
2
|
require 'limiter'
|
3
3
|
|
4
|
-
Fog::DNS::
|
5
|
-
Fog::DNS::
|
4
|
+
Fog::Dynect::DNS::Real.extend(Limiter::Mixin)
|
5
|
+
Fog::Dynect::DNS::Real.limit_method(:request, rate: 5, interval: 1) # 5 RPS == 300 RPM
|
6
6
|
|
7
7
|
module RecordStore
|
8
8
|
class Provider::DynECT < Provider
|
@@ -23,7 +23,8 @@ module RecordStore
|
|
23
23
|
def record(zone:, fqdn:, type:, must_exist: false)
|
24
24
|
result = super(zone, fqdn, type)
|
25
25
|
raise_if_error!(result) if must_exist
|
26
|
-
return
|
26
|
+
return if result.is_a?(NS1::Response::Error)
|
27
|
+
|
27
28
|
result
|
28
29
|
end
|
29
30
|
|
@@ -52,6 +53,7 @@ module RecordStore
|
|
52
53
|
if result.is_a?(NS1::Response::UnparsableBodyError)
|
53
54
|
raise RecordStore::Provider::UnparseableBodyError, result.to_s
|
54
55
|
end
|
56
|
+
|
55
57
|
raise RecordStore::Provider::Error, result.to_s
|
56
58
|
end
|
57
59
|
end
|
@@ -19,7 +19,7 @@ module NS1::Transport
|
|
19
19
|
|
20
20
|
if response_hash.key?(X_RATELIMIT_PERIOD) && response_hash.key?(X_RATELIMIT_REMAINING)
|
21
21
|
sleep_time = response_hash[X_RATELIMIT_PERIOD].first.to_i /
|
22
|
-
|
22
|
+
[1, response_hash[X_RATELIMIT_REMAINING].first.to_i].max.to_f
|
23
23
|
|
24
24
|
rate_limit = RateLimitWaiter.new('NS1')
|
25
25
|
rate_limit.wait(sleep_time)
|
@@ -87,7 +87,7 @@ module RecordStore
|
|
87
87
|
existing_record = client.record(
|
88
88
|
zone: zone,
|
89
89
|
fqdn: record_fqdn,
|
90
|
-
type: record.type
|
90
|
+
type: record.type,
|
91
91
|
)
|
92
92
|
|
93
93
|
if existing_record.nil?
|
@@ -99,7 +99,7 @@ module RecordStore
|
|
99
99
|
answers: new_answers,
|
100
100
|
ttl: record.ttl,
|
101
101
|
use_client_subnet: false, # only required for filter chains that are not supported by record_store
|
102
|
-
}
|
102
|
+
},
|
103
103
|
)
|
104
104
|
return
|
105
105
|
end
|
@@ -109,7 +109,7 @@ module RecordStore
|
|
109
109
|
zone: zone,
|
110
110
|
fqdn: record_fqdn,
|
111
111
|
type: record.type,
|
112
|
-
params: { answers: existing_answers + new_answers, ttl: record.ttl }
|
112
|
+
params: { answers: existing_answers + new_answers, ttl: record.ttl },
|
113
113
|
)
|
114
114
|
end
|
115
115
|
|
@@ -123,7 +123,7 @@ module RecordStore
|
|
123
123
|
existing_record = client.record(
|
124
124
|
zone: zone,
|
125
125
|
fqdn: record_fqdn,
|
126
|
-
type: record.type
|
126
|
+
type: record.type,
|
127
127
|
)
|
128
128
|
return if existing_record.nil?
|
129
129
|
|
@@ -135,7 +135,7 @@ module RecordStore
|
|
135
135
|
client.delete_record(
|
136
136
|
zone: zone,
|
137
137
|
fqdn: record_fqdn,
|
138
|
-
type: record.type
|
138
|
+
type: record.type,
|
139
139
|
)
|
140
140
|
return
|
141
141
|
end
|
@@ -144,7 +144,7 @@ module RecordStore
|
|
144
144
|
zone: zone,
|
145
145
|
fqdn: record_fqdn,
|
146
146
|
type: record.type,
|
147
|
-
params: { answers: pruned_answers }
|
147
|
+
params: { answers: pruned_answers },
|
148
148
|
)
|
149
149
|
end
|
150
150
|
|
@@ -188,7 +188,7 @@ module RecordStore
|
|
188
188
|
zone: zone,
|
189
189
|
fqdn: record_fqdn,
|
190
190
|
type: record.type,
|
191
|
-
params: { answers: existing_record['answers'], ttl: record.ttl }
|
191
|
+
params: { answers: existing_record['answers'], ttl: record.ttl },
|
192
192
|
)
|
193
193
|
end
|
194
194
|
|
@@ -55,7 +55,7 @@ module RecordStore
|
|
55
55
|
|
56
56
|
client.patch_zone_records(
|
57
57
|
zone,
|
58
|
-
OCI::Dns::Models::PatchZoneRecordsDetails.new(items: patch_add_record)
|
58
|
+
OCI::Dns::Models::PatchZoneRecordsDetails.new(items: patch_add_record),
|
59
59
|
)
|
60
60
|
end
|
61
61
|
|
@@ -72,6 +72,7 @@ module RecordStore
|
|
72
72
|
).data.items.select { |r| r.rdata == record.rdata_txt }
|
73
73
|
|
74
74
|
return unless found_record
|
75
|
+
|
75
76
|
begin
|
76
77
|
record_hash = found_record.first.record_hash if found_record.length == 1
|
77
78
|
rescue NoMethodError
|
@@ -90,7 +91,7 @@ module RecordStore
|
|
90
91
|
|
91
92
|
client.patch_zone_records(
|
92
93
|
zone,
|
93
|
-
OCI::Dns::Models::PatchZoneRecordsDetails.new(items: patch_remove_record)
|
94
|
+
OCI::Dns::Models::PatchZoneRecordsDetails.new(items: patch_remove_record),
|
94
95
|
)
|
95
96
|
end
|
96
97
|
end
|
@@ -122,7 +123,7 @@ module RecordStore
|
|
122
123
|
domain: record_fqdn,
|
123
124
|
ttl: record.ttl,
|
124
125
|
rtype: record.type,
|
125
|
-
rdata: record.rdata_txt
|
126
|
+
rdata: record.rdata_txt,
|
126
127
|
)
|
127
128
|
update_zone_record_items.delete_if { |r| id == r.record_hash }
|
128
129
|
|
@@ -8,6 +8,7 @@ module RecordStore
|
|
8
8
|
|
9
9
|
class << self
|
10
10
|
def provider_for(object)
|
11
|
+
lookup_error = false
|
11
12
|
ns_server =
|
12
13
|
case object
|
13
14
|
when Record::NS
|
@@ -17,9 +18,10 @@ module RecordStore
|
|
17
18
|
master_nameserver_for(object)
|
18
19
|
rescue Resolv::ResolvError
|
19
20
|
$stderr.puts "Domain doesn't exist (#{object})"
|
20
|
-
|
21
|
+
lookup_error = true
|
21
22
|
end
|
22
23
|
end
|
24
|
+
return if lookup_error
|
23
25
|
|
24
26
|
case ns_server
|
25
27
|
when /\.dnsimple\.com\z/
|
@@ -156,16 +158,19 @@ module RecordStore
|
|
156
158
|
return yield
|
157
159
|
rescue UnparseableBodyError
|
158
160
|
raise if max_retries <= 0
|
161
|
+
|
159
162
|
max_retries -= 1
|
160
163
|
|
161
164
|
waiter.wait(message: 'Waiting to retry after receiving an unparseable response')
|
162
165
|
rescue Net::OpenTimeout, Errno::ETIMEDOUT
|
163
166
|
raise if max_timeouts <= 0
|
167
|
+
|
164
168
|
max_timeouts -= 1
|
165
169
|
|
166
170
|
$stderr.puts('Retrying after a connection timeout')
|
167
171
|
rescue Errno::ECONNRESET
|
168
172
|
raise if max_conn_resets <= 0
|
173
|
+
|
169
174
|
max_conn_resets -= 1
|
170
175
|
|
171
176
|
waiter.wait
|
@@ -7,7 +7,8 @@ module RecordStore
|
|
7
7
|
|
8
8
|
validates :flags, presence: true, numericality:
|
9
9
|
{
|
10
|
-
only_integer: true,
|
10
|
+
only_integer: true,
|
11
|
+
greater_than_or_equal_to: 0,
|
11
12
|
less_than_or_equal_to: 255
|
12
13
|
}
|
13
14
|
validates :tag, inclusion: { in: %w(issue issuewild iodef) }, presence: true
|
@@ -42,6 +43,7 @@ module RecordStore
|
|
42
43
|
def validate_uri_value
|
43
44
|
uri = URI(value)
|
44
45
|
return if uri.is_a?(URI::MailTo) || uri.is_a?(URI::HTTP)
|
46
|
+
|
45
47
|
errors.add(:value, "URL scheme should be mailto, http, or https")
|
46
48
|
rescue URI::Error
|
47
49
|
errors.add(:value, "Value should be a valid URI")
|
data/lib/record_store/record.rb
CHANGED
@@ -61,12 +61,14 @@ module RecordStore
|
|
61
61
|
@id = record.fetch(:record_id, nil)
|
62
62
|
end
|
63
63
|
|
64
|
-
|
65
|
-
|
66
|
-
|
64
|
+
class << self
|
65
|
+
def build_from_yaml_definition(yaml_definition)
|
66
|
+
record_type = yaml_definition.fetch(:type)
|
67
|
+
Record.const_get(record_type).new(yaml_definition)
|
68
|
+
end
|
67
69
|
end
|
68
70
|
|
69
|
-
def log!(logger =
|
71
|
+
def log!(logger = $stdout)
|
70
72
|
logger.puts to_s
|
71
73
|
end
|
72
74
|
|
data/lib/record_store/version.rb
CHANGED
@@ -1,10 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module RecordStore
|
3
4
|
class Zone
|
4
5
|
class Config
|
5
6
|
class TemplateContext
|
6
|
-
|
7
|
-
|
7
|
+
class << self
|
8
|
+
def build(record:, current_records:)
|
9
|
+
new(record: record, current_records: current_records)
|
10
|
+
end
|
8
11
|
end
|
9
12
|
|
10
13
|
def initialize(record:, current_records:)
|
@@ -31,9 +34,11 @@ module RecordStore
|
|
31
34
|
filters_for_records_to_template = template_file_yaml[:each_record]
|
32
35
|
filters_for_records_to_exclude = template_file_yaml[:except_record] || []
|
33
36
|
|
34
|
-
new(
|
37
|
+
new(
|
38
|
+
template: ERB.new(template_file),
|
35
39
|
filters_for_records_to_template: filters_for_records_to_template,
|
36
|
-
filters_for_records_to_exclude: filters_for_records_to_exclude
|
40
|
+
filters_for_records_to_exclude: filters_for_records_to_exclude,
|
41
|
+
)
|
37
42
|
end
|
38
43
|
|
39
44
|
private
|
@@ -56,11 +61,11 @@ module RecordStore
|
|
56
61
|
.each_with_object([]) do |template_records, records_to_inject|
|
57
62
|
next unless should_inject?(
|
58
63
|
template_records: template_records,
|
59
|
-
current_records: current_records + records_to_inject
|
64
|
+
current_records: current_records + records_to_inject,
|
60
65
|
)
|
61
66
|
|
62
67
|
records_to_inject.push(
|
63
|
-
*template_records.fetch(:injected_records, []).map { |r_yml| Record.build_from_yaml_definition(r_yml) }
|
68
|
+
*template_records.fetch(:injected_records, []).map { |r_yml| Record.build_from_yaml_definition(r_yml) },
|
64
69
|
)
|
65
70
|
end
|
66
71
|
end
|
@@ -79,7 +84,7 @@ module RecordStore
|
|
79
84
|
end
|
80
85
|
|
81
86
|
def should_template?(record:)
|
82
|
-
filters_for_records_to_template.any? { |filter| record_match?(record: record, filter: filter) } &&
|
87
|
+
filters_for_records_to_template.any? { |filter| record_match?(record: record, filter: filter) } &&
|
83
88
|
filters_for_records_to_exclude.none? { |filter| record_match?(record: record, filter: filter) }
|
84
89
|
end
|
85
90
|
|
@@ -32,6 +32,7 @@ module RecordStore
|
|
32
32
|
|
33
33
|
def write(name, config:, records:, format: :file)
|
34
34
|
raise ArgumentError, "format must be :directory or :file" unless [:file, :directory].include?(format)
|
35
|
+
|
35
36
|
name = name.chomp('.')
|
36
37
|
zone_file = "#{RecordStore.zones_path}/#{name}.yml"
|
37
38
|
zone = { name => { config: config.to_hash } }
|
@@ -60,6 +61,7 @@ module RecordStore
|
|
60
61
|
dir = File.dirname(filename)
|
61
62
|
data = YAML.load_file(filename)
|
62
63
|
raise 'more than one zone in file' if data.size > 1
|
64
|
+
|
63
65
|
name, definition = data.first
|
64
66
|
definition['records'] ||= []
|
65
67
|
definition['records'] = definition['records'].map(&:deep_symbolize_keys)
|
data/lib/record_store/zone.rb
CHANGED
@@ -33,7 +33,7 @@ module RecordStore
|
|
33
33
|
zone.config = Zone::Config.new(
|
34
34
|
providers: [provider_name],
|
35
35
|
ignore_patterns: [{ type: "NS", fqdn: "#{name}." }],
|
36
|
-
supports_alias:
|
36
|
+
supports_alias: zone.records.map(&:type).include?('ALIAS') || nil,
|
37
37
|
)
|
38
38
|
|
39
39
|
zone.write(**write_options)
|
@@ -144,24 +144,22 @@ module RecordStore
|
|
144
144
|
authority = fetch_soa(nameserver) do |reply, _name|
|
145
145
|
break if reply.answer.any?
|
146
146
|
|
147
|
-
raise "No authority found (#{name})"
|
147
|
+
raise "No authority found (#{name})" if reply.authority.none?
|
148
148
|
|
149
149
|
break extract_authority(reply.authority)
|
150
150
|
end
|
151
151
|
|
152
152
|
# candidate DNS name is returned instead when NXDomain or other error
|
153
|
-
return
|
153
|
+
return if unrooted_name.casecmp?(Array(authority).first.to_s)
|
154
154
|
|
155
155
|
authority
|
156
156
|
end
|
157
157
|
|
158
158
|
private
|
159
159
|
|
160
|
-
def fetch_soa(nameserver)
|
160
|
+
def fetch_soa(nameserver, &block)
|
161
161
|
Resolv::DNS.open(nameserver: nameserver) do |resolv|
|
162
|
-
resolv.fetch_resource(name, Resolv::DNS::Resource::IN::SOA
|
163
|
-
yield reply, name
|
164
|
-
end
|
162
|
+
resolv.fetch_resource(name, Resolv::DNS::Resource::IN::SOA, &block)
|
165
163
|
end
|
166
164
|
end
|
167
165
|
|
@@ -174,6 +172,7 @@ module RecordStore
|
|
174
172
|
rescue Errno::EHOSTUNREACH => e
|
175
173
|
$stderr.puts "Warning: #{e} [host=#{nameserver}]"
|
176
174
|
raise if nameservers.empty?
|
175
|
+
|
177
176
|
retry
|
178
177
|
end
|
179
178
|
end
|
@@ -244,6 +243,7 @@ module RecordStore
|
|
244
243
|
cname_records.each do |cname_record|
|
245
244
|
records.each do |record|
|
246
245
|
next unless record.fqdn == cname_record.fqdn && record != cname_record
|
246
|
+
|
247
247
|
case record.type
|
248
248
|
when 'SIG', 'NXT', 'KEY'
|
249
249
|
# this is fine
|
@@ -284,12 +284,13 @@ module RecordStore
|
|
284
284
|
|
285
285
|
nameserver_fqdns.each do |ns_record|
|
286
286
|
selected_records = records.reject do |record|
|
287
|
-
record.is_a?(Record::NS) &&
|
287
|
+
record.is_a?(Record::NS) &&
|
288
288
|
record.fqdn.delete_suffix(".") == ns_record
|
289
289
|
end
|
290
290
|
selected_records.each do |record|
|
291
291
|
normalized_record = record.fqdn.delete_suffix(".")
|
292
292
|
next unless normalized_record.end_with?(".#{ns_record}") || normalized_record == ns_record
|
293
|
+
|
293
294
|
errors.add(:records, "Record #{record.fqdn} #{record.type} in Zone #{name} " \
|
294
295
|
"is shadowed by #{ns_record} and will be ignored")
|
295
296
|
end
|
@@ -305,14 +306,14 @@ module RecordStore
|
|
305
306
|
|
306
307
|
terminal_records = records.map(&:fqdn)
|
307
308
|
.select { |record| record.match?(/^([a-zA-Z0-9\-_]+\.[a-zA-Z0-9\-_])#{Regexp.escape(suffix)}$/) }
|
308
|
-
next
|
309
|
+
next if terminal_records.none?
|
309
310
|
|
310
311
|
intermediate_records = records.map(&:fqdn)
|
311
312
|
.select { |record| record.match?(/^([a-zA-Z0-9\-_]+)#{Regexp.escape(suffix)}$/) }
|
312
313
|
terminal_records.each do |terminal_record|
|
313
314
|
non_terminal = terminal_record.partition('.').last
|
314
315
|
errors.add(:records, "found empty non-terminal #{non_terminal} "\
|
315
|
-
|
316
|
+
"(caused by existing records #{wildcard} and #{terminal_record})")\
|
316
317
|
unless intermediate_records.include?(non_terminal)
|
317
318
|
end
|
318
319
|
end
|
data/lib/record_store.rb
CHANGED
@@ -52,8 +52,10 @@ module RecordStore
|
|
52
52
|
|
53
53
|
def zones_path
|
54
54
|
@zones_path ||= Pathname.new(
|
55
|
-
File.expand_path(
|
56
|
-
|
55
|
+
File.expand_path(
|
56
|
+
config.fetch('zones_path'),
|
57
|
+
File.dirname(config_path),
|
58
|
+
),
|
57
59
|
).realpath.to_s
|
58
60
|
end
|
59
61
|
|
@@ -63,8 +65,10 @@ module RecordStore
|
|
63
65
|
|
64
66
|
def implicit_records_templates_path
|
65
67
|
@implicit_records_templates_path ||= Pathname.new(
|
66
|
-
File.expand_path(
|
67
|
-
|
68
|
+
File.expand_path(
|
69
|
+
config.fetch('implicit_records_templates_path'),
|
70
|
+
File.dirname(config_path),
|
71
|
+
),
|
68
72
|
).realpath.to_s
|
69
73
|
end
|
70
74
|
|
data/record_store.gemspec
CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
|
|
10
10
|
|
11
11
|
spec.summary = 'Manage DNS using git'
|
12
12
|
spec.description = "Manage DNS through a git-based workflow. If you're looking for the original 'record_store',"\
|
13
|
-
|
13
|
+
" that has been renamed to 'sequel_record_store'."
|
14
14
|
spec.homepage = 'https://github.com/Shopify/record_store'
|
15
15
|
spec.license = 'MIT'
|
16
16
|
|
@@ -26,28 +26,28 @@ Gem::Specification.new do |spec|
|
|
26
26
|
|
27
27
|
spec.required_ruby_version = '>= 2.0'
|
28
28
|
|
29
|
-
spec.add_runtime_dependency 'thor', '~> 0.20.3'
|
30
|
-
spec.add_runtime_dependency 'activesupport', '>= 4.2'
|
31
29
|
spec.add_runtime_dependency 'activemodel', '>= 4.2'
|
30
|
+
spec.add_runtime_dependency 'activesupport', '>= 4.2'
|
32
31
|
spec.add_runtime_dependency 'ejson'
|
32
|
+
spec.add_runtime_dependency 'thor', '>= 0.20.3', '< 1.4.0'
|
33
33
|
|
34
|
+
spec.add_runtime_dependency 'dnsimple', '>= 4.4', '< 8.8'
|
35
|
+
spec.add_runtime_dependency 'fog-dynect', '>= 0.4', '< 0.6'
|
34
36
|
spec.add_runtime_dependency 'fog-json'
|
35
37
|
spec.add_runtime_dependency 'fog-xml'
|
36
|
-
spec.add_runtime_dependency 'fog-dynect', '~> 0.4.0'
|
37
|
-
spec.add_runtime_dependency 'dnsimple', '~> 4.4.0'
|
38
38
|
spec.add_runtime_dependency 'google-cloud-dns', '~> 0.31'
|
39
|
-
spec.add_runtime_dependency 'ruby-limiter', '~> 1.0', '>= 1.0.1'
|
40
39
|
spec.add_runtime_dependency 'ns1'
|
41
40
|
spec.add_runtime_dependency 'oci', '~> 2.14.0'
|
41
|
+
spec.add_runtime_dependency 'ruby-limiter', '>= 1.0.1', '< 3'
|
42
42
|
|
43
|
-
spec.add_development_dependency 'byebug'
|
44
|
-
spec.add_development_dependency 'rake'
|
45
43
|
spec.add_development_dependency 'bundler'
|
44
|
+
spec.add_development_dependency 'byebug'
|
45
|
+
spec.add_development_dependency 'minitest-focus'
|
46
46
|
spec.add_development_dependency 'mocha'
|
47
|
-
spec.add_development_dependency 'vcr'
|
48
47
|
spec.add_development_dependency 'pry'
|
48
|
+
spec.add_development_dependency 'rake'
|
49
|
+
spec.add_development_dependency 'rubocop', '~> 1.59.0'
|
50
|
+
spec.add_development_dependency 'rubocop-shopify', '~> 2.14.0'
|
51
|
+
spec.add_development_dependency 'vcr'
|
49
52
|
spec.add_development_dependency 'webmock'
|
50
|
-
spec.add_development_dependency 'rubocop', '~> 1.18.0'
|
51
|
-
spec.add_development_dependency 'rubocop-shopify', '~> 2.2.0'
|
52
|
-
spec.add_development_dependency 'minitest-focus'
|
53
53
|
end
|
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.
|
4
|
+
version: 6.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Willem van Bergen
|
@@ -9,24 +9,10 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2024-01-18 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
|
-
name:
|
16
|
-
requirement: !ruby/object:Gem::Requirement
|
17
|
-
requirements:
|
18
|
-
- - "~>"
|
19
|
-
- !ruby/object:Gem::Version
|
20
|
-
version: 0.20.3
|
21
|
-
type: :runtime
|
22
|
-
prerelease: false
|
23
|
-
version_requirements: !ruby/object:Gem::Requirement
|
24
|
-
requirements:
|
25
|
-
- - "~>"
|
26
|
-
- !ruby/object:Gem::Version
|
27
|
-
version: 0.20.3
|
28
|
-
- !ruby/object:Gem::Dependency
|
29
|
-
name: activesupport
|
15
|
+
name: activemodel
|
30
16
|
requirement: !ruby/object:Gem::Requirement
|
31
17
|
requirements:
|
32
18
|
- - ">="
|
@@ -40,7 +26,7 @@ dependencies:
|
|
40
26
|
- !ruby/object:Gem::Version
|
41
27
|
version: '4.2'
|
42
28
|
- !ruby/object:Gem::Dependency
|
43
|
-
name:
|
29
|
+
name: activesupport
|
44
30
|
requirement: !ruby/object:Gem::Requirement
|
45
31
|
requirements:
|
46
32
|
- - ">="
|
@@ -68,95 +54,107 @@ dependencies:
|
|
68
54
|
- !ruby/object:Gem::Version
|
69
55
|
version: '0'
|
70
56
|
- !ruby/object:Gem::Dependency
|
71
|
-
name:
|
57
|
+
name: thor
|
72
58
|
requirement: !ruby/object:Gem::Requirement
|
73
59
|
requirements:
|
74
60
|
- - ">="
|
75
61
|
- !ruby/object:Gem::Version
|
76
|
-
version:
|
62
|
+
version: 0.20.3
|
63
|
+
- - "<"
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: 1.4.0
|
77
66
|
type: :runtime
|
78
67
|
prerelease: false
|
79
68
|
version_requirements: !ruby/object:Gem::Requirement
|
80
69
|
requirements:
|
81
70
|
- - ">="
|
82
71
|
- !ruby/object:Gem::Version
|
83
|
-
version:
|
72
|
+
version: 0.20.3
|
73
|
+
- - "<"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 1.4.0
|
84
76
|
- !ruby/object:Gem::Dependency
|
85
|
-
name:
|
77
|
+
name: dnsimple
|
86
78
|
requirement: !ruby/object:Gem::Requirement
|
87
79
|
requirements:
|
88
80
|
- - ">="
|
89
81
|
- !ruby/object:Gem::Version
|
90
|
-
version: '
|
82
|
+
version: '4.4'
|
83
|
+
- - "<"
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '8.8'
|
91
86
|
type: :runtime
|
92
87
|
prerelease: false
|
93
88
|
version_requirements: !ruby/object:Gem::Requirement
|
94
89
|
requirements:
|
95
90
|
- - ">="
|
96
91
|
- !ruby/object:Gem::Version
|
97
|
-
version: '
|
92
|
+
version: '4.4'
|
93
|
+
- - "<"
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '8.8'
|
98
96
|
- !ruby/object:Gem::Dependency
|
99
97
|
name: fog-dynect
|
100
98
|
requirement: !ruby/object:Gem::Requirement
|
101
99
|
requirements:
|
102
|
-
- - "
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0.4'
|
103
|
+
- - "<"
|
103
104
|
- !ruby/object:Gem::Version
|
104
|
-
version: 0.
|
105
|
+
version: '0.6'
|
105
106
|
type: :runtime
|
106
107
|
prerelease: false
|
107
108
|
version_requirements: !ruby/object:Gem::Requirement
|
108
109
|
requirements:
|
109
|
-
- - "
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '0.4'
|
113
|
+
- - "<"
|
110
114
|
- !ruby/object:Gem::Version
|
111
|
-
version: 0.
|
115
|
+
version: '0.6'
|
112
116
|
- !ruby/object:Gem::Dependency
|
113
|
-
name:
|
117
|
+
name: fog-json
|
114
118
|
requirement: !ruby/object:Gem::Requirement
|
115
119
|
requirements:
|
116
|
-
- - "
|
120
|
+
- - ">="
|
117
121
|
- !ruby/object:Gem::Version
|
118
|
-
version:
|
122
|
+
version: '0'
|
119
123
|
type: :runtime
|
120
124
|
prerelease: false
|
121
125
|
version_requirements: !ruby/object:Gem::Requirement
|
122
126
|
requirements:
|
123
|
-
- - "
|
127
|
+
- - ">="
|
124
128
|
- !ruby/object:Gem::Version
|
125
|
-
version:
|
129
|
+
version: '0'
|
126
130
|
- !ruby/object:Gem::Dependency
|
127
|
-
name:
|
131
|
+
name: fog-xml
|
128
132
|
requirement: !ruby/object:Gem::Requirement
|
129
133
|
requirements:
|
130
|
-
- - "
|
134
|
+
- - ">="
|
131
135
|
- !ruby/object:Gem::Version
|
132
|
-
version: '0
|
136
|
+
version: '0'
|
133
137
|
type: :runtime
|
134
138
|
prerelease: false
|
135
139
|
version_requirements: !ruby/object:Gem::Requirement
|
136
140
|
requirements:
|
137
|
-
- - "
|
141
|
+
- - ">="
|
138
142
|
- !ruby/object:Gem::Version
|
139
|
-
version: '0
|
143
|
+
version: '0'
|
140
144
|
- !ruby/object:Gem::Dependency
|
141
|
-
name:
|
145
|
+
name: google-cloud-dns
|
142
146
|
requirement: !ruby/object:Gem::Requirement
|
143
147
|
requirements:
|
144
148
|
- - "~>"
|
145
149
|
- !ruby/object:Gem::Version
|
146
|
-
version: '
|
147
|
-
- - ">="
|
148
|
-
- !ruby/object:Gem::Version
|
149
|
-
version: 1.0.1
|
150
|
+
version: '0.31'
|
150
151
|
type: :runtime
|
151
152
|
prerelease: false
|
152
153
|
version_requirements: !ruby/object:Gem::Requirement
|
153
154
|
requirements:
|
154
155
|
- - "~>"
|
155
156
|
- !ruby/object:Gem::Version
|
156
|
-
version: '
|
157
|
-
- - ">="
|
158
|
-
- !ruby/object:Gem::Version
|
159
|
-
version: 1.0.1
|
157
|
+
version: '0.31'
|
160
158
|
- !ruby/object:Gem::Dependency
|
161
159
|
name: ns1
|
162
160
|
requirement: !ruby/object:Gem::Requirement
|
@@ -186,21 +184,27 @@ dependencies:
|
|
186
184
|
- !ruby/object:Gem::Version
|
187
185
|
version: 2.14.0
|
188
186
|
- !ruby/object:Gem::Dependency
|
189
|
-
name:
|
187
|
+
name: ruby-limiter
|
190
188
|
requirement: !ruby/object:Gem::Requirement
|
191
189
|
requirements:
|
192
190
|
- - ">="
|
193
191
|
- !ruby/object:Gem::Version
|
194
|
-
version:
|
195
|
-
|
192
|
+
version: 1.0.1
|
193
|
+
- - "<"
|
194
|
+
- !ruby/object:Gem::Version
|
195
|
+
version: '3'
|
196
|
+
type: :runtime
|
196
197
|
prerelease: false
|
197
198
|
version_requirements: !ruby/object:Gem::Requirement
|
198
199
|
requirements:
|
199
200
|
- - ">="
|
200
201
|
- !ruby/object:Gem::Version
|
201
|
-
version:
|
202
|
+
version: 1.0.1
|
203
|
+
- - "<"
|
204
|
+
- !ruby/object:Gem::Version
|
205
|
+
version: '3'
|
202
206
|
- !ruby/object:Gem::Dependency
|
203
|
-
name:
|
207
|
+
name: bundler
|
204
208
|
requirement: !ruby/object:Gem::Requirement
|
205
209
|
requirements:
|
206
210
|
- - ">="
|
@@ -214,7 +218,7 @@ dependencies:
|
|
214
218
|
- !ruby/object:Gem::Version
|
215
219
|
version: '0'
|
216
220
|
- !ruby/object:Gem::Dependency
|
217
|
-
name:
|
221
|
+
name: byebug
|
218
222
|
requirement: !ruby/object:Gem::Requirement
|
219
223
|
requirements:
|
220
224
|
- - ">="
|
@@ -228,7 +232,7 @@ dependencies:
|
|
228
232
|
- !ruby/object:Gem::Version
|
229
233
|
version: '0'
|
230
234
|
- !ruby/object:Gem::Dependency
|
231
|
-
name:
|
235
|
+
name: minitest-focus
|
232
236
|
requirement: !ruby/object:Gem::Requirement
|
233
237
|
requirements:
|
234
238
|
- - ">="
|
@@ -242,7 +246,7 @@ dependencies:
|
|
242
246
|
- !ruby/object:Gem::Version
|
243
247
|
version: '0'
|
244
248
|
- !ruby/object:Gem::Dependency
|
245
|
-
name:
|
249
|
+
name: mocha
|
246
250
|
requirement: !ruby/object:Gem::Requirement
|
247
251
|
requirements:
|
248
252
|
- - ">="
|
@@ -270,7 +274,7 @@ dependencies:
|
|
270
274
|
- !ruby/object:Gem::Version
|
271
275
|
version: '0'
|
272
276
|
- !ruby/object:Gem::Dependency
|
273
|
-
name:
|
277
|
+
name: rake
|
274
278
|
requirement: !ruby/object:Gem::Requirement
|
275
279
|
requirements:
|
276
280
|
- - ">="
|
@@ -289,30 +293,44 @@ dependencies:
|
|
289
293
|
requirements:
|
290
294
|
- - "~>"
|
291
295
|
- !ruby/object:Gem::Version
|
292
|
-
version: 1.
|
296
|
+
version: 1.59.0
|
293
297
|
type: :development
|
294
298
|
prerelease: false
|
295
299
|
version_requirements: !ruby/object:Gem::Requirement
|
296
300
|
requirements:
|
297
301
|
- - "~>"
|
298
302
|
- !ruby/object:Gem::Version
|
299
|
-
version: 1.
|
303
|
+
version: 1.59.0
|
300
304
|
- !ruby/object:Gem::Dependency
|
301
305
|
name: rubocop-shopify
|
302
306
|
requirement: !ruby/object:Gem::Requirement
|
303
307
|
requirements:
|
304
308
|
- - "~>"
|
305
309
|
- !ruby/object:Gem::Version
|
306
|
-
version: 2.
|
310
|
+
version: 2.14.0
|
307
311
|
type: :development
|
308
312
|
prerelease: false
|
309
313
|
version_requirements: !ruby/object:Gem::Requirement
|
310
314
|
requirements:
|
311
315
|
- - "~>"
|
312
316
|
- !ruby/object:Gem::Version
|
313
|
-
version: 2.
|
317
|
+
version: 2.14.0
|
314
318
|
- !ruby/object:Gem::Dependency
|
315
|
-
name:
|
319
|
+
name: vcr
|
320
|
+
requirement: !ruby/object:Gem::Requirement
|
321
|
+
requirements:
|
322
|
+
- - ">="
|
323
|
+
- !ruby/object:Gem::Version
|
324
|
+
version: '0'
|
325
|
+
type: :development
|
326
|
+
prerelease: false
|
327
|
+
version_requirements: !ruby/object:Gem::Requirement
|
328
|
+
requirements:
|
329
|
+
- - ">="
|
330
|
+
- !ruby/object:Gem::Version
|
331
|
+
version: '0'
|
332
|
+
- !ruby/object:Gem::Dependency
|
333
|
+
name: webmock
|
316
334
|
requirement: !ruby/object:Gem::Requirement
|
317
335
|
requirements:
|
318
336
|
- - ">="
|
@@ -335,8 +353,10 @@ executables:
|
|
335
353
|
extensions: []
|
336
354
|
extra_rdoc_files: []
|
337
355
|
files:
|
356
|
+
- ".github/dependabot.yml"
|
338
357
|
- ".github/workflows/ci.yml"
|
339
358
|
- ".github/workflows/cla.yml"
|
359
|
+
- ".github/workflows/stale-action-handling.yml"
|
340
360
|
- ".gitignore"
|
341
361
|
- ".rubocop-https---shopify-github-io-ruby-style-guide-rubocop-yml"
|
342
362
|
- ".rubocop.yml"
|
@@ -419,7 +439,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
419
439
|
- !ruby/object:Gem::Version
|
420
440
|
version: '0'
|
421
441
|
requirements: []
|
422
|
-
rubygems_version: 3.4
|
442
|
+
rubygems_version: 3.5.4
|
423
443
|
signing_key:
|
424
444
|
specification_version: 4
|
425
445
|
summary: Manage DNS using git
|