record_store 5.4.3 → 5.5.3

Sign up to get free protection for your applications and to get access to all the features.
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