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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d88da404a1f4360b15693ee929e1479c5ab7bfef551a97c911519eced767690d
4
- data.tar.gz: 9df6b7b314b1183bb1e8396e3f59223792e3fba54396ce12a7499d0258e5e8bb
3
+ metadata.gz: c86a82574e2e61abc72654d54c264dfe15ea0c8668822e0f7733380f75288daa
4
+ data.tar.gz: 0fde940c2527255c8e96d003b9157d6619336ae79b5ef3264aa03be89b91955c
5
5
  SHA512:
6
- metadata.gz: ec002b78039daff7ccd252c0e2ca0a1acfda84b2ee7e8cb8b41dac7f895a4d1abd33abe35b7f407af2e21b6e5d23999460be35012ff5913c0048bb58428ee110
7
- data.tar.gz: a334e12a947772bee93a83d6b91c0db886063593e218e18ab584fce687c0b268ffe38a8be77e7aad29ba257eb71f511189fc5e291664fd466eacc6166d123fab
6
+ metadata.gz: e403a99e4ecfac5230b7ac8f1d875648b28e095d5e28d4952b303dd970793b243ab8fc7f34a7468ecc2647242ce0b70c3432210ee3d5fa7f646b395c1c132663
7
+ data.tar.gz: 2e9aadb049dd91de8dd96c6fe5dc09eb558e06313a29df7424aa9d764b467d8b1c0cfec6d956f36f78082bd5c01d78d6ab1a0249dfee809b22ffe428617a92f5
data/.gitignore CHANGED
@@ -4,3 +4,4 @@ dev/
4
4
  pkg/
5
5
  Gemfile.lock
6
6
  .bundle/config
7
+ .byebug_history
@@ -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
@@ -19,6 +19,8 @@ module RecordStore
19
19
  'DynECT'
20
20
  when /googledomains\.com\z/
21
21
  'GoogleCloudDNS'
22
+ when /\.nsone\.net\z/
23
+ 'NS1'
22
24
  else
23
25
  nil
24
26
  end
@@ -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
@@ -1,3 +1,3 @@
1
1
  module RecordStore
2
- VERSION = '5.4.3'.freeze
2
+ VERSION = '5.5.3'.freeze
3
3
  end
@@ -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'
@@ -9,6 +9,9 @@
9
9
  "account_id": "dnsimple_account_id",
10
10
  "api_token": "dnsimple_api_token"
11
11
  },
12
+ "ns1": {
13
+ "api_key": "ns1_api_key"
14
+ },
12
15
  "google_cloud_dns": {
13
16
  "type": "type",
14
17
  "project_id": "project_id",
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.3
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-03-19 00:00:00.000000000 Z
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
- rubyforge_project:
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