record_store 5.0.4 → 5.0.5
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/CHANGELOG.md +13 -2
- data/README.md +29 -22
- data/dev.yml +1 -1
- data/lib/record_store/changeset.rb +4 -0
- data/lib/record_store/provider.rb +27 -31
- data/lib/record_store/provider/dnsimple.rb +21 -21
- data/lib/record_store/provider/dynect.rb +39 -29
- data/lib/record_store/provider/google_cloud_dns.rb +3 -2
- data/lib/record_store/record.rb +2 -3
- data/lib/record_store/record/spf.rb +2 -18
- data/lib/record_store/record/txt.rb +19 -5
- data/lib/record_store/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8898e68756f44cc7777bfbbff84d9806eeace0ed
|
4
|
+
data.tar.gz: 861fc60403a348b1c2c2fe0bf551a935e1a747ff
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fbc65b2ee1998c5f13bd28025892b1433716e0c74c1e7e44a50b6e5550ecb66913acda076224a85c4059dcc878835bb02ec195adc80460a9c1177b8c46408d2d
|
7
|
+
data.tar.gz: ea7ab36879e1c6cae29573f9d51140a00e4f7a447563764f877dc4556b23f4f81bffe2f193137587f2c359011d72239ccb8a3b38750cd3163743540881917b86
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,17 @@
|
|
1
1
|
# CHANGELOG
|
2
2
|
|
3
|
+
## 5.0.5
|
4
|
+
- Output progress messages for GoogleCloudDNS provider too. [BUGFIX]
|
5
|
+
- Fix quoting/escaping for TXT records. [BUGFIX]
|
6
|
+
- Make implementation-specific methods of Provider private. [REFACTOR]
|
7
|
+
- DRY up SPF support to use TXT superclass implementation. [REFACTOR]
|
8
|
+
|
3
9
|
## 5.0.4
|
4
|
-
- Replaces fog-dnsimple with dnsimple-ruby gem.
|
10
|
+
- Replaces fog-dnsimple with dnsimple-ruby gem. [REFACTOR]
|
11
|
+
|
12
|
+
## 5.0.0
|
13
|
+
- Use DNSimple API v2 (via fog-dnsimple gem update).
|
14
|
+
|
15
|
+
## 4.0.7
|
5
16
|
|
6
|
-
|
17
|
+
- Fix issue updating records with same FQDN. [BUGFIX]
|
data/README.md
CHANGED
@@ -96,28 +96,6 @@ Provider API interactions are tested with [VCR](https://github.com/vcr/vcr). To
|
|
96
96
|
Outline of [`Provider`](lib/record_store/provider.rb):
|
97
97
|
```ruby
|
98
98
|
class Provider
|
99
|
-
# Creates a new record to the zone. It is expected this call modifies external state.
|
100
|
-
#
|
101
|
-
# Arguments:
|
102
|
-
# record - a kind of `Record`
|
103
|
-
def add(record)
|
104
|
-
end
|
105
|
-
|
106
|
-
# Deletes an existing record from the zone. It is expected this call modifies external state.
|
107
|
-
#
|
108
|
-
# Arguments:
|
109
|
-
# record - a kind of `Record`
|
110
|
-
def remove(record)
|
111
|
-
end
|
112
|
-
|
113
|
-
# Updates an existing record in the zone. It is expected this call modifies external state.
|
114
|
-
#
|
115
|
-
# Arguments:
|
116
|
-
# id - provider specific ID of record to update
|
117
|
-
# record - a kind of `Record` which the record with `id` should be updated to
|
118
|
-
def update(id, record)
|
119
|
-
end
|
120
|
-
|
121
99
|
# Downloads all the records from the provider.
|
122
100
|
#
|
123
101
|
# Returns: an array of `Record` for each record in the provider's zone
|
@@ -141,6 +119,35 @@ class Provider
|
|
141
119
|
# Unlocks the zone to allow making changes (see `Provider#freeze_zone`).
|
142
120
|
def thaw
|
143
121
|
end
|
122
|
+
|
123
|
+
private
|
124
|
+
|
125
|
+
######## NOTE ########
|
126
|
+
# The following methods only need to be implemented if you are using the base provider's
|
127
|
+
# implementation of apply_changeset to manage the contents of the changeset (or transaction).
|
128
|
+
######################
|
129
|
+
|
130
|
+
# Creates a new record to the zone. It is expected this call modifies external state.
|
131
|
+
#
|
132
|
+
# Arguments:
|
133
|
+
# record - a kind of `Record`
|
134
|
+
def add(record)
|
135
|
+
end
|
136
|
+
|
137
|
+
# Deletes an existing record from the zone. It is expected this call modifies external state.
|
138
|
+
#
|
139
|
+
# Arguments:
|
140
|
+
# record - a kind of `Record`
|
141
|
+
def remove(record)
|
142
|
+
end
|
143
|
+
|
144
|
+
# Updates an existing record in the zone. It is expected this call modifies external state.
|
145
|
+
#
|
146
|
+
# Arguments:
|
147
|
+
# id - provider specific ID of record to update
|
148
|
+
# record - a kind of `Record` which the record with `id` should be updated to
|
149
|
+
def update(id, record)
|
150
|
+
end
|
144
151
|
end
|
145
152
|
```
|
146
153
|
|
data/dev.yml
CHANGED
@@ -57,7 +57,11 @@ module RecordStore
|
|
57
57
|
end
|
58
58
|
|
59
59
|
def apply
|
60
|
+
puts "Applying #{additions.size} additions, #{removals.size} removals, and #{updates.size} updates..."
|
61
|
+
|
60
62
|
provider.apply_changeset(self)
|
63
|
+
|
64
|
+
puts "Published #{zone} changes to #{provider}\n\n"
|
61
65
|
end
|
62
66
|
|
63
67
|
def unchanged
|
@@ -55,40 +55,22 @@ module RecordStore
|
|
55
55
|
raise NotImplementedError
|
56
56
|
end
|
57
57
|
|
58
|
-
def add(record)
|
59
|
-
raise NotImplementedError
|
60
|
-
end
|
61
|
-
|
62
|
-
def remove(record)
|
63
|
-
raise NotImplementedError
|
64
|
-
end
|
65
|
-
|
66
|
-
def update(id, record)
|
67
|
-
raise NotImplementedError
|
68
|
-
end
|
69
|
-
|
70
58
|
# Applies changeset to provider
|
71
59
|
def apply_changeset(changeset, stdout = $stdout)
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
update(change.id, change.record, changeset.zone)
|
86
|
-
else
|
87
|
-
raise ArgumentError, "Unknown change type #{change.type.inspect}"
|
88
|
-
end
|
60
|
+
changeset.changes.each do |change|
|
61
|
+
case change.type
|
62
|
+
when :removal
|
63
|
+
stdout.puts "Removing #{change.record}..."
|
64
|
+
remove(change.record, changeset.zone)
|
65
|
+
when :addition
|
66
|
+
stdout.puts "Creating #{change.record}..."
|
67
|
+
add(change.record, changeset.zone)
|
68
|
+
when :update
|
69
|
+
stdout.puts "Updating record with ID #{change.id} to #{change.record}..."
|
70
|
+
update(change.id, change.record, changeset.zone)
|
71
|
+
else
|
72
|
+
raise ArgumentError, "Unknown change type #{change.type.inspect}"
|
89
73
|
end
|
90
|
-
|
91
|
-
puts "\nPublished #{changeset.zone} changes to #{changeset.provider.to_s}\n"
|
92
74
|
end
|
93
75
|
end
|
94
76
|
|
@@ -116,6 +98,20 @@ module RecordStore
|
|
116
98
|
def to_s
|
117
99
|
self.name.demodulize
|
118
100
|
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
def add(record)
|
105
|
+
raise NotImplementedError
|
106
|
+
end
|
107
|
+
|
108
|
+
def remove(record)
|
109
|
+
raise NotImplementedError
|
110
|
+
end
|
111
|
+
|
112
|
+
def update(id, record)
|
113
|
+
raise NotImplementedError
|
114
|
+
end
|
119
115
|
end
|
120
116
|
end
|
121
117
|
end
|
@@ -7,6 +7,25 @@ module RecordStore
|
|
7
7
|
true
|
8
8
|
end
|
9
9
|
|
10
|
+
# returns an array of Record objects that match the records which exist in the provider
|
11
|
+
def retrieve_current_records(zone:, stdout: $stdout)
|
12
|
+
session.zones.all_records(account_id, zone).data.map do |record|
|
13
|
+
begin
|
14
|
+
build_from_api(record, zone)
|
15
|
+
rescue StandardError
|
16
|
+
stdout.puts "Cannot build record: #{record}"
|
17
|
+
raise
|
18
|
+
end
|
19
|
+
end.compact
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns an array of the zones managed by provider as strings
|
23
|
+
def zones
|
24
|
+
session.zones.all_zones(account_id).data.map(&:name)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
10
29
|
def add(record, zone)
|
11
30
|
record_hash = api_hash(record, zone)
|
12
31
|
res = session.zones.create_record(account_id, zone, record_hash)
|
@@ -29,25 +48,6 @@ module RecordStore
|
|
29
48
|
session.zones.update_record(account_id, zone, id, api_hash(record, zone))
|
30
49
|
end
|
31
50
|
|
32
|
-
# returns an array of Record objects that match the records which exist in the provider
|
33
|
-
def retrieve_current_records(zone:, stdout: $stdout)
|
34
|
-
session.zones.all_records(account_id, zone).data.map do |record|
|
35
|
-
begin
|
36
|
-
build_from_api(record, zone)
|
37
|
-
rescue StandardError
|
38
|
-
stdout.puts "Cannot build record: #{record}"
|
39
|
-
raise
|
40
|
-
end
|
41
|
-
end.compact
|
42
|
-
end
|
43
|
-
|
44
|
-
# Returns an array of the zones managed by provider as strings
|
45
|
-
def zones
|
46
|
-
session.zones.all_zones(account_id).data.map(&:name)
|
47
|
-
end
|
48
|
-
|
49
|
-
private
|
50
|
-
|
51
51
|
def session
|
52
52
|
@dns ||= Dnsimple::Client.new(
|
53
53
|
base_url: secrets.fetch('base_url'),
|
@@ -85,7 +85,7 @@ module RecordStore
|
|
85
85
|
when 'NS'
|
86
86
|
record.merge!(nsdname: api_record.content)
|
87
87
|
when 'SPF', 'TXT'
|
88
|
-
record.merge!(txtdata: api_record.content.gsub(';', '\;'))
|
88
|
+
record.merge!(txtdata: Record::TXT.unescape(api_record.content).gsub(';', '\;'))
|
89
89
|
when 'SRV'
|
90
90
|
weight, port, host = api_record.content.split(' ')
|
91
91
|
|
@@ -124,7 +124,7 @@ module RecordStore
|
|
124
124
|
when 'NS'
|
125
125
|
record_hash[:content] = record.nsdname.chomp('.')
|
126
126
|
when 'SPF', 'TXT'
|
127
|
-
record_hash[:content] = record.txtdata.gsub('\;', ';')
|
127
|
+
record_hash[:content] = Record::TXT.escape(record.txtdata).gsub('\;', ';')
|
128
128
|
when 'SRV'
|
129
129
|
record_hash[:content] = "#{record.weight} #{record.port} #{record.target.chomp('.')}"
|
130
130
|
record_hash[:priority] = record.priority
|
@@ -11,35 +11,21 @@ module RecordStore
|
|
11
11
|
session.put_zone(zone, thaw: true)
|
12
12
|
end
|
13
13
|
|
14
|
-
def add(record, zone)
|
15
|
-
session.post_record(record.type, zone, record.fqdn, record.rdata, 'ttl' => record.ttl)
|
16
|
-
end
|
17
|
-
|
18
|
-
def remove(record, zone)
|
19
|
-
session.delete_record(record.type, zone, record.fqdn, record.id)
|
20
|
-
end
|
21
|
-
|
22
|
-
def update(id, record, zone)
|
23
|
-
session.put_record(record.type, zone, record.fqdn, record.rdata, 'ttl' => record.ttl, 'record_id' => id)
|
24
|
-
end
|
25
|
-
|
26
14
|
def publish(zone)
|
27
15
|
session.put_zone(zone, publish: true)
|
28
16
|
end
|
29
17
|
|
30
18
|
# Applies changeset to provider
|
31
19
|
def apply_changeset(changeset, stdout = $stdout)
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
freeze_zone(changeset.zone)
|
42
|
-
end
|
20
|
+
thaw_zone(changeset.zone)
|
21
|
+
super
|
22
|
+
publish(changeset.zone)
|
23
|
+
rescue StandardError
|
24
|
+
puts "An exception occurred while applying DNS changes, deleting changeset"
|
25
|
+
discard_change_set(changeset.zone)
|
26
|
+
raise
|
27
|
+
ensure
|
28
|
+
freeze_zone(changeset.zone)
|
43
29
|
end
|
44
30
|
|
45
31
|
# returns an array of Record objects that match the records which exist in the provider
|
@@ -62,6 +48,18 @@ module RecordStore
|
|
62
48
|
|
63
49
|
private
|
64
50
|
|
51
|
+
def add(record, zone)
|
52
|
+
session.post_record(record.type, zone, record.fqdn, api_rdata(record), 'ttl' => record.ttl)
|
53
|
+
end
|
54
|
+
|
55
|
+
def remove(record, zone)
|
56
|
+
session.delete_record(record.type, zone, record.fqdn, record.id)
|
57
|
+
end
|
58
|
+
|
59
|
+
def update(id, record, zone)
|
60
|
+
session.put_record(record.type, zone, record.fqdn, api_rdata(record), 'ttl' => record.ttl, 'record_id' => id)
|
61
|
+
end
|
62
|
+
|
65
63
|
def discard_change_set(zone)
|
66
64
|
session.request(expects: 200, method: :delete, path: "ZoneChanges/#{zone}")
|
67
65
|
end
|
@@ -84,16 +82,28 @@ module RecordStore
|
|
84
82
|
super.fetch('dynect')
|
85
83
|
end
|
86
84
|
|
85
|
+
def api_rdata(record)
|
86
|
+
case record.type
|
87
|
+
when 'TXT'
|
88
|
+
{ txtdata: record.rdata_txt }
|
89
|
+
else
|
90
|
+
record.rdata
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
87
94
|
def build_from_api(api_record)
|
88
|
-
|
95
|
+
rdata = api_record.fetch('rdata')
|
96
|
+
record = api_record.merge(rdata).slice!('rdata').symbolize_keys
|
89
97
|
|
90
|
-
|
98
|
+
type = record.fetch(:record_type)
|
99
|
+
return if type == 'SOA'
|
91
100
|
|
92
|
-
|
93
|
-
|
94
|
-
|
101
|
+
record[:txtdata] = Record::TXT.unescape(record[:txtdata]) if %w[SPF TXT].include?(type)
|
102
|
+
|
103
|
+
fqdn = record.fetch(:fqdn)
|
104
|
+
record[:fqdn] = "#{fqdn}." unless fqdn.ends_with?('.')
|
95
105
|
|
96
|
-
Record.const_get(
|
106
|
+
Record.const_get(type).new(record)
|
97
107
|
end
|
98
108
|
end
|
99
109
|
end
|
@@ -9,7 +9,7 @@ module RecordStore
|
|
9
9
|
deletions = convert_records_to_gcloud_record_sets(zone, changeset.current_records)
|
10
10
|
additions = convert_records_to_gcloud_record_sets(zone, changeset.desired_records)
|
11
11
|
|
12
|
-
# The Google API library will handle applying the changeset
|
12
|
+
# The Google API library will handle applying the changeset transactionally
|
13
13
|
zone.update(additions, deletions)
|
14
14
|
end
|
15
15
|
|
@@ -98,7 +98,8 @@ module RecordStore
|
|
98
98
|
when 'NS'
|
99
99
|
record_params.merge!(nsdname: record.data[0])
|
100
100
|
when 'SPF', 'TXT'
|
101
|
-
|
101
|
+
txtdata = Record::TXT.unquote(record.data[0]).gsub(';', '\;')
|
102
|
+
record_params.merge!(txtdata: txtdata)
|
102
103
|
when 'SRV'
|
103
104
|
priority, weight, port, target = record.data[0].split(' ')
|
104
105
|
|
data/lib/record_store/record.rb
CHANGED
@@ -35,7 +35,7 @@ module RecordStore
|
|
35
35
|
end
|
36
36
|
|
37
37
|
def type
|
38
|
-
self.class.name.
|
38
|
+
self.class.name.demodulize
|
39
39
|
end
|
40
40
|
|
41
41
|
def ==(other)
|
@@ -65,8 +65,7 @@ module RecordStore
|
|
65
65
|
end
|
66
66
|
|
67
67
|
def to_s
|
68
|
-
|
69
|
-
"[#{rr_type}Record] #{fqdn} #{ttl} IN #{rr_type} #{rdata_txt}"
|
68
|
+
"[#{type}Record] #{fqdn} #{ttl} IN #{type} #{rdata_txt}"
|
70
69
|
end
|
71
70
|
|
72
71
|
protected
|
@@ -1,24 +1,8 @@
|
|
1
1
|
module RecordStore
|
2
|
-
class Record::SPF < Record
|
3
|
-
attr_accessor :txtdata
|
4
|
-
|
5
|
-
validates :txtdata, presence: true, length: { maximum: 255 }
|
6
|
-
|
2
|
+
class Record::SPF < Record::TXT
|
7
3
|
def initialize(record)
|
4
|
+
STDERR.puts "SPF record type is deprecated (See RFC 7208 Section 14.1)"
|
8
5
|
super
|
9
|
-
@txtdata = record.fetch(:txtdata)
|
10
|
-
end
|
11
|
-
|
12
|
-
def to_s
|
13
|
-
"[SPFRecord] #{fqdn} #{ttl} IN SPF \"#{rdata_txt}\""
|
14
|
-
end
|
15
|
-
|
16
|
-
def rdata
|
17
|
-
{ txtdata: txtdata }
|
18
|
-
end
|
19
|
-
|
20
|
-
def rdata_txt
|
21
|
-
txtdata
|
22
6
|
end
|
23
7
|
end
|
24
8
|
end
|
@@ -5,21 +5,35 @@ module RecordStore
|
|
5
5
|
validates :txtdata, presence: true, length: { maximum: 255 }
|
6
6
|
validate :escaped_semicolons
|
7
7
|
|
8
|
+
class << self
|
9
|
+
def escape(value)
|
10
|
+
value.gsub('"', '\"')
|
11
|
+
end
|
12
|
+
|
13
|
+
def quote(value)
|
14
|
+
%("#{escape(value)}")
|
15
|
+
end
|
16
|
+
|
17
|
+
def unescape(value)
|
18
|
+
value.gsub('\"', '"')
|
19
|
+
end
|
20
|
+
|
21
|
+
def unquote(value)
|
22
|
+
unescape(value.sub(/\A"(.*)"\z/, '\1'))
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
8
26
|
def initialize(record)
|
9
27
|
super
|
10
28
|
@txtdata = record.fetch(:txtdata)
|
11
29
|
end
|
12
30
|
|
13
|
-
def to_s
|
14
|
-
"[TXTRecord] #{fqdn} #{ttl} IN TXT \"#{rdata_txt}\""
|
15
|
-
end
|
16
|
-
|
17
31
|
def rdata
|
18
32
|
{ txtdata: txtdata }
|
19
33
|
end
|
20
34
|
|
21
35
|
def rdata_txt
|
22
|
-
txtdata
|
36
|
+
Record::TXT.quote(txtdata)
|
23
37
|
end
|
24
38
|
|
25
39
|
private
|
data/lib/record_store/version.rb
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.0.
|
4
|
+
version: 5.0.5
|
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: 2018-02
|
12
|
+
date: 2018-04-02 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: thor
|