record_store 6.6.0 → 6.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|