record_store 5.4.3 → 5.5.3
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/.gitignore +1 -0
- data/lib/record_store.rb +1 -0
- data/lib/record_store/provider.rb +2 -0
- data/lib/record_store/provider/ns1.rb +220 -0
- data/lib/record_store/provider/ns1/client.rb +47 -0
- data/lib/record_store/version.rb +1 -1
- data/record_store.gemspec +3 -0
- data/template/secrets.json +3 -0
- metadata +33 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c86a82574e2e61abc72654d54c264dfe15ea0c8668822e0f7733380f75288daa
|
4
|
+
data.tar.gz: 0fde940c2527255c8e96d003b9157d6619336ae79b5ef3264aa03be89b91955c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e403a99e4ecfac5230b7ac8f1d875648b28e095d5e28d4952b303dd970793b243ab8fc7f34a7468ecc2647242ce0b70c3432210ee3d5fa7f646b395c1c132663
|
7
|
+
data.tar.gz: 2e9aadb049dd91de8dd96c6fe5dc09eb558e06313a29df7424aa9d764b467d8b1c0cfec6d956f36f78082bd5c01d78d6ab1a0249dfee809b22ffe428617a92f5
|
data/.gitignore
CHANGED
data/lib/record_store.rb
CHANGED
@@ -31,6 +31,7 @@ require 'record_store/provider'
|
|
31
31
|
require 'record_store/provider/dynect'
|
32
32
|
require 'record_store/provider/dnsimple'
|
33
33
|
require 'record_store/provider/google_cloud_dns'
|
34
|
+
require 'record_store/provider/ns1'
|
34
35
|
require 'record_store/cli'
|
35
36
|
|
36
37
|
module RecordStore
|
@@ -0,0 +1,220 @@
|
|
1
|
+
require_relative 'ns1/client'
|
2
|
+
|
3
|
+
module RecordStore
|
4
|
+
class Provider::NS1 < Provider
|
5
|
+
class Error < StandardError; end
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def client
|
9
|
+
Provider::NS1::Client.new(api_key: secrets['api_key'])
|
10
|
+
end
|
11
|
+
|
12
|
+
# Downloads all the records from the provider.
|
13
|
+
#
|
14
|
+
# Returns: an array of `Record` for each record in the provider's zone
|
15
|
+
def retrieve_current_records(zone:, stdout: $stdout)
|
16
|
+
full_api_records = records_for_zone(zone).map do |short_record|
|
17
|
+
client.record(
|
18
|
+
zone: zone,
|
19
|
+
fqdn: short_record["domain"],
|
20
|
+
type: short_record["type"],
|
21
|
+
must_exist: true,
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
full_api_records.map { |r| build_from_api(r, zone) }.flatten.compact
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns an array of the zones managed by provider as strings
|
29
|
+
def zones
|
30
|
+
client.zones.map { |zone| zone['zone'] }
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
# Fetches simplified records for the provided zone
|
36
|
+
def records_for_zone(zone)
|
37
|
+
client.zone(zone)["records"]
|
38
|
+
end
|
39
|
+
|
40
|
+
# Creates a new record to the zone. It is expected this call modifies external state.
|
41
|
+
#
|
42
|
+
# Arguments:
|
43
|
+
# record - a kind of `Record`
|
44
|
+
def add(record, zone)
|
45
|
+
new_answers = [{ answer: build_api_answer_from_record(record) }]
|
46
|
+
|
47
|
+
record_fqdn = record.fqdn.sub(/\.$/, '')
|
48
|
+
|
49
|
+
existing_record = client.record(
|
50
|
+
zone: zone,
|
51
|
+
fqdn: record_fqdn,
|
52
|
+
type: record.type
|
53
|
+
)
|
54
|
+
|
55
|
+
if existing_record.nil?
|
56
|
+
client.create_record(
|
57
|
+
zone: zone,
|
58
|
+
fqdn: record_fqdn,
|
59
|
+
type: record.type,
|
60
|
+
params: { answers: new_answers, ttl: record.ttl }
|
61
|
+
)
|
62
|
+
return
|
63
|
+
end
|
64
|
+
|
65
|
+
existing_answers = existing_record['answers'].map { |answer| symbolize_keys(answer) }
|
66
|
+
client.modify_record(
|
67
|
+
zone: zone,
|
68
|
+
fqdn: record_fqdn,
|
69
|
+
type: record.type,
|
70
|
+
params: { answers: existing_answers + new_answers, ttl: record.ttl }
|
71
|
+
)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Deletes an existing record from the zone. It is expected this call modifies external state.
|
75
|
+
#
|
76
|
+
# Arguments:
|
77
|
+
# record - a kind of `Record`
|
78
|
+
def remove(record, zone)
|
79
|
+
record_fqdn = record.fqdn.sub(/\.$/, '')
|
80
|
+
|
81
|
+
existing_record = client.record(
|
82
|
+
zone: zone,
|
83
|
+
fqdn: record_fqdn,
|
84
|
+
type: record.type
|
85
|
+
)
|
86
|
+
return if existing_record.nil?
|
87
|
+
|
88
|
+
pruned_answers = existing_record['answers']
|
89
|
+
.map { |answer| symbolize_keys(answer) }
|
90
|
+
.reject { |answer| answer[:answer] == build_api_answer_from_record(record) }
|
91
|
+
|
92
|
+
if pruned_answers.empty?
|
93
|
+
client.delete_record(
|
94
|
+
zone: zone,
|
95
|
+
fqdn: record_fqdn,
|
96
|
+
type: record.type
|
97
|
+
)
|
98
|
+
return
|
99
|
+
end
|
100
|
+
|
101
|
+
client.modify_record(
|
102
|
+
zone: zone,
|
103
|
+
fqdn: record_fqdn,
|
104
|
+
type: record.type,
|
105
|
+
params: { answers: pruned_answers }
|
106
|
+
)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Updates an existing record in the zone. It is expected this call modifies external state.
|
110
|
+
#
|
111
|
+
# Arguments:
|
112
|
+
# id - provider specific ID of record to update
|
113
|
+
# record - a kind of `Record` which the record with `id` should be updated to
|
114
|
+
def update(id, record, zone)
|
115
|
+
record_fqdn = record.fqdn.sub(/\.$/, '')
|
116
|
+
|
117
|
+
existing_record = client.record(
|
118
|
+
zone: zone,
|
119
|
+
fqdn: record_fqdn,
|
120
|
+
type: record.type,
|
121
|
+
must_exist: true,
|
122
|
+
)
|
123
|
+
|
124
|
+
# Identify the answer in this record with the matching ID, and update it
|
125
|
+
updated = false
|
126
|
+
existing_record["answers"].each do |answer|
|
127
|
+
next if answer["id"] != id
|
128
|
+
updated = true
|
129
|
+
answer["answer"] = build_api_answer_from_record(record)
|
130
|
+
end
|
131
|
+
|
132
|
+
raise(Error, "while trying to update a record, could not find answer with fqdn: #{record.fqdn}, type; #{record.type}, id: #{id}") unless updated
|
133
|
+
|
134
|
+
client.modify_record(
|
135
|
+
zone: zone,
|
136
|
+
fqdn: record_fqdn,
|
137
|
+
type: record.type,
|
138
|
+
params: { answers: existing_record["answers"], ttl: record.ttl }
|
139
|
+
)
|
140
|
+
end
|
141
|
+
|
142
|
+
def build_from_api(api_record, zone)
|
143
|
+
fqdn = Record.ensure_ends_with_dot(api_record["domain"])
|
144
|
+
|
145
|
+
record_type = api_record["type"]
|
146
|
+
return if record_type == 'SOA'
|
147
|
+
|
148
|
+
api_record["answers"].map do |api_answer|
|
149
|
+
answer = api_answer["answer"]
|
150
|
+
record = {
|
151
|
+
ttl: api_record["ttl"],
|
152
|
+
fqdn: fqdn.downcase,
|
153
|
+
record_id: api_answer["id"]
|
154
|
+
}
|
155
|
+
|
156
|
+
case record_type
|
157
|
+
when 'A', 'AAAA'
|
158
|
+
record.merge!(address: answer.first)
|
159
|
+
when 'ALIAS'
|
160
|
+
record.merge!(alias: answer.first)
|
161
|
+
when 'CAA'
|
162
|
+
flags, tag, value = answer
|
163
|
+
|
164
|
+
record.merge!(
|
165
|
+
flags: flags.to_i,
|
166
|
+
tag: tag,
|
167
|
+
value: Record.unquote(value),
|
168
|
+
)
|
169
|
+
when 'CNAME'
|
170
|
+
record.merge!(cname: answer.first)
|
171
|
+
when 'MX'
|
172
|
+
|
173
|
+
preference, exchange = answer
|
174
|
+
|
175
|
+
record.merge!(
|
176
|
+
preference: preference.to_i,
|
177
|
+
exchange: exchange,
|
178
|
+
)
|
179
|
+
when 'NS'
|
180
|
+
record.merge!(nsdname: answer.first)
|
181
|
+
when 'SPF', 'TXT'
|
182
|
+
record.merge!(txtdata: Record.unescape(answer.first).gsub(';', '\;'))
|
183
|
+
when 'SRV'
|
184
|
+
priority, weight, port, host = answer
|
185
|
+
|
186
|
+
record.merge!(
|
187
|
+
priority: priority.to_i,
|
188
|
+
weight: weight.to_i,
|
189
|
+
port: port.to_i,
|
190
|
+
target: Record.ensure_ends_with_dot(host),
|
191
|
+
)
|
192
|
+
end
|
193
|
+
Record.const_get(record_type).new(record)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def build_api_answer_from_record(record)
|
198
|
+
if record.is_a?(Record::MX)
|
199
|
+
[record.preference, record.exchange]
|
200
|
+
elsif record.is_a?(Record::TXT) or record.is_a?(Record::SPF)
|
201
|
+
[record.txtdata]
|
202
|
+
elsif record.is_a?(Record::CAA)
|
203
|
+
[record.flags, record.tag, record.value]
|
204
|
+
elsif record.is_a?(Record::SRV)
|
205
|
+
[record.priority, record.weight, record.port, record.target]
|
206
|
+
else
|
207
|
+
[record.rdata_txt]
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def symbolize_keys(hash)
|
212
|
+
hash.map{ |key, value| [key.to_sym, value] }.to_h
|
213
|
+
end
|
214
|
+
|
215
|
+
def secrets
|
216
|
+
super.fetch('ns1')
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'ns1'
|
2
|
+
|
3
|
+
module RecordStore
|
4
|
+
class Provider::NS1 < Provider
|
5
|
+
class Error < StandardError; end
|
6
|
+
|
7
|
+
class Client < ::NS1::Client
|
8
|
+
def initialize(api_key:)
|
9
|
+
super(api_key)
|
10
|
+
end
|
11
|
+
|
12
|
+
def zones
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
def zone(name)
|
17
|
+
super(name)
|
18
|
+
end
|
19
|
+
|
20
|
+
def record(zone:, fqdn:, type:, must_exist: false)
|
21
|
+
result = super(zone, fqdn, type)
|
22
|
+
raise(Error, result.to_s) if must_exist && result.is_a?(NS1::Response::Error)
|
23
|
+
return nil if result.is_a?(NS1::Response::Error)
|
24
|
+
result
|
25
|
+
end
|
26
|
+
|
27
|
+
def create_record(zone:, fqdn:, type:, params:)
|
28
|
+
result = super(zone, fqdn, type, params)
|
29
|
+
raise(Error, result.to_s) if result.is_a? NS1::Response::Error
|
30
|
+
nil
|
31
|
+
end
|
32
|
+
|
33
|
+
def modify_record(zone:, fqdn:, type:, params:)
|
34
|
+
result = super(zone, fqdn, type, params)
|
35
|
+
raise(Error, result.to_s) if result.is_a? NS1::Response::Error
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
|
39
|
+
def delete_record(zone:, fqdn:, type:)
|
40
|
+
result = super(zone, fqdn, type)
|
41
|
+
raise(Error, result.to_s) if result.is_a? NS1::Response::Error
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
data/lib/record_store/version.rb
CHANGED
data/record_store.gemspec
CHANGED
@@ -31,7 +31,10 @@ Gem::Specification.new do |spec|
|
|
31
31
|
spec.add_runtime_dependency 'dnsimple', '~> 4.4.0'
|
32
32
|
spec.add_runtime_dependency 'google-cloud-dns'
|
33
33
|
spec.add_runtime_dependency 'ruby-limiter', '~> 1.0', '>= 1.0.1'
|
34
|
+
spec.add_runtime_dependency 'ns1'
|
34
35
|
|
36
|
+
|
37
|
+
spec.add_development_dependency 'byebug'
|
35
38
|
spec.add_development_dependency 'rake'
|
36
39
|
spec.add_development_dependency 'bundler'
|
37
40
|
spec.add_development_dependency 'mocha'
|
data/template/secrets.json
CHANGED
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.
|
4
|
+
version: 5.5.3
|
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: 2019-
|
12
|
+
date: 2019-09-30 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: thor
|
@@ -171,6 +171,34 @@ dependencies:
|
|
171
171
|
- - ">="
|
172
172
|
- !ruby/object:Gem::Version
|
173
173
|
version: 1.0.1
|
174
|
+
- !ruby/object:Gem::Dependency
|
175
|
+
name: ns1
|
176
|
+
requirement: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ">="
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
181
|
+
type: :runtime
|
182
|
+
prerelease: false
|
183
|
+
version_requirements: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - ">="
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: '0'
|
188
|
+
- !ruby/object:Gem::Dependency
|
189
|
+
name: byebug
|
190
|
+
requirement: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - ">="
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: '0'
|
195
|
+
type: :development
|
196
|
+
prerelease: false
|
197
|
+
version_requirements: !ruby/object:Gem::Requirement
|
198
|
+
requirements:
|
199
|
+
- - ">="
|
200
|
+
- !ruby/object:Gem::Version
|
201
|
+
version: '0'
|
174
202
|
- !ruby/object:Gem::Dependency
|
175
203
|
name: rake
|
176
204
|
requirement: !ruby/object:Gem::Requirement
|
@@ -284,6 +312,8 @@ files:
|
|
284
312
|
- lib/record_store/provider/dnsimple.rb
|
285
313
|
- lib/record_store/provider/dynect.rb
|
286
314
|
- lib/record_store/provider/google_cloud_dns.rb
|
315
|
+
- lib/record_store/provider/ns1.rb
|
316
|
+
- lib/record_store/provider/ns1/client.rb
|
287
317
|
- lib/record_store/record.rb
|
288
318
|
- lib/record_store/record/a.rb
|
289
319
|
- lib/record_store/record/aaaa.rb
|
@@ -332,8 +362,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
332
362
|
- !ruby/object:Gem::Version
|
333
363
|
version: '0'
|
334
364
|
requirements: []
|
335
|
-
|
336
|
-
rubygems_version: 2.7.6
|
365
|
+
rubygems_version: 3.0.3
|
337
366
|
signing_key:
|
338
367
|
specification_version: 4
|
339
368
|
summary: Manage DNS using git
|