record_store 2.1.0 → 3.0.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/bin/console +3 -2
- data/lib/record_store.rb +2 -0
- data/lib/record_store/changeset.rb +22 -3
- data/lib/record_store/cli.rb +59 -66
- data/lib/record_store/provider.rb +92 -82
- data/lib/record_store/provider/dnsimple.rb +123 -133
- data/lib/record_store/provider/dynect.rb +73 -71
- data/lib/record_store/record.rb +1 -0
- data/lib/record_store/version.rb +1 -1
- data/lib/record_store/zone.rb +62 -135
- data/lib/record_store/zone/config.rb +16 -9
- data/lib/record_store/zone/yaml_definitions.rb +105 -0
- metadata +3 -2
@@ -2,157 +2,147 @@ require 'fog/dnsimple'
|
|
2
2
|
|
3
3
|
module RecordStore
|
4
4
|
class Provider::DNSimple < Provider
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
def add(record)
|
10
|
-
record_hash = api_hash(record)
|
11
|
-
res = session.create_record(
|
12
|
-
@zone_name,
|
13
|
-
record_hash.fetch(:name),
|
14
|
-
record.type,
|
15
|
-
record_hash.fetch(:content),
|
16
|
-
ttl: record_hash.fetch(:ttl),
|
17
|
-
priority: record_hash.fetch(:prio, nil)
|
18
|
-
)
|
19
|
-
|
20
|
-
if record.type == 'ALIAS'
|
21
|
-
txt_alias = retrieve_current_records.detect do |rr|
|
22
|
-
rr.type == 'TXT' && rr.fqdn == record.fqdn && rr.txtdata == "ALIAS for #{record.alias.chomp('.')}"
|
23
|
-
end
|
24
|
-
remove(txt_alias)
|
5
|
+
class << self
|
6
|
+
def supports_alias?
|
7
|
+
true
|
25
8
|
end
|
26
9
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
end
|
38
|
-
|
39
|
-
# returns an array of Record objects that match the records which exist in the provider
|
40
|
-
def retrieve_current_records(stdout = $stdout)
|
41
|
-
session.list_records(@zone_name).body.map do |record|
|
42
|
-
record_body = record.fetch('record')
|
10
|
+
def add(record, zone)
|
11
|
+
record_hash = api_hash(record, zone)
|
12
|
+
res = session.create_record(
|
13
|
+
zone,
|
14
|
+
record_hash.fetch(:name),
|
15
|
+
record.type,
|
16
|
+
record_hash.fetch(:content),
|
17
|
+
ttl: record_hash.fetch(:ttl),
|
18
|
+
priority: record_hash.fetch(:prio, nil)
|
19
|
+
)
|
43
20
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
21
|
+
if record.type == 'ALIAS'
|
22
|
+
txt_alias = retrieve_current_records(zone: zone).detect do |rr|
|
23
|
+
rr.type == 'TXT' && rr.fqdn == record.fqdn && rr.txtdata == "ALIAS for #{record.alias.chomp('.')}"
|
24
|
+
end
|
25
|
+
remove(txt_alias, zone)
|
49
26
|
end
|
50
|
-
end.select(&:present?)
|
51
|
-
end
|
52
27
|
|
53
|
-
|
54
|
-
|
55
|
-
session.zones.map(&:domain)
|
56
|
-
end
|
28
|
+
res
|
29
|
+
end
|
57
30
|
|
58
|
-
|
31
|
+
def remove(record, zone)
|
32
|
+
session.delete_record(zone, record.id)
|
33
|
+
end
|
59
34
|
|
60
|
-
|
61
|
-
|
62
|
-
|
35
|
+
def update(id, record, zone)
|
36
|
+
record_hash = api_hash(record, zone)
|
37
|
+
session.update_record(zone, id, api_hash(record, zone))
|
38
|
+
end
|
63
39
|
|
64
|
-
|
65
|
-
|
66
|
-
|
40
|
+
# returns an array of Record objects that match the records which exist in the provider
|
41
|
+
def retrieve_current_records(zone:, stdout: $stdout)
|
42
|
+
session.list_records(zone).body.map do |record|
|
43
|
+
record_body = record.fetch('record')
|
44
|
+
|
45
|
+
begin
|
46
|
+
build_from_api(record_body, zone)
|
47
|
+
rescue StandardError
|
48
|
+
stdout.puts "Cannot build record: #{record_body}"
|
49
|
+
raise
|
50
|
+
end
|
51
|
+
end.select(&:present?)
|
52
|
+
end
|
67
53
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
dnsimple_token: secrets.fetch('api_token'),
|
73
|
-
}
|
74
|
-
end
|
54
|
+
# Returns an array of the zones managed by provider as strings
|
55
|
+
def zones
|
56
|
+
session.zones.map(&:domain)
|
57
|
+
end
|
75
58
|
|
76
|
-
|
77
|
-
super.fetch('dnsimple')
|
78
|
-
end
|
59
|
+
private
|
79
60
|
|
80
|
-
|
81
|
-
|
82
|
-
record = {
|
83
|
-
record_id: api_record.fetch('id'),
|
84
|
-
ttl: api_record.fetch('ttl'),
|
85
|
-
fqdn: api_record.fetch('name').present? ? "#{api_record.fetch('name')}.#{@zone_name}" : @zone_name,
|
86
|
-
}
|
87
|
-
|
88
|
-
return if record_type == 'SOA'
|
89
|
-
|
90
|
-
case record_type
|
91
|
-
when 'A'
|
92
|
-
record.merge!(address: api_record.fetch('content'))
|
93
|
-
when 'AAAA'
|
94
|
-
record.merge!(address: api_record.fetch('content'))
|
95
|
-
when 'ALIAS'
|
96
|
-
record.merge!(alias: api_record.fetch('content'))
|
97
|
-
when 'CNAME'
|
98
|
-
record.merge!(cname: api_record.fetch('content'))
|
99
|
-
when 'MX'
|
100
|
-
record.merge!(preference: api_record.fetch('prio'), exchange: api_record.fetch('content'))
|
101
|
-
when 'NS'
|
102
|
-
record.merge!(nsdname: api_record.fetch('content'))
|
103
|
-
when 'SPF'
|
104
|
-
record.merge!(txtdata: api_record.fetch('content'))
|
105
|
-
when 'SRV'
|
106
|
-
weight, port, host = api_record.fetch('content').split(' ')
|
107
|
-
|
108
|
-
record.merge!(
|
109
|
-
priority: api_record.fetch('prio'),
|
110
|
-
weight: weight,
|
111
|
-
port: port,
|
112
|
-
target: Record.ensure_ends_with_dot(host),
|
113
|
-
)
|
114
|
-
when 'TXT'
|
115
|
-
record.merge!(txtdata: api_record.fetch('content'))
|
61
|
+
def session
|
62
|
+
@dns ||= Fog::DNS.new(session_params)
|
116
63
|
end
|
117
64
|
|
118
|
-
|
119
|
-
|
65
|
+
def session_params
|
66
|
+
{
|
67
|
+
provider: 'DNSimple',
|
68
|
+
dnsimple_email: secrets.fetch('email'),
|
69
|
+
dnsimple_token: secrets.fetch('api_token'),
|
70
|
+
}
|
120
71
|
end
|
121
72
|
|
122
|
-
|
123
|
-
|
73
|
+
def secrets
|
74
|
+
super.fetch('dnsimple')
|
75
|
+
end
|
124
76
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
77
|
+
def build_from_api(api_record, zone)
|
78
|
+
record_type = api_record.fetch('record_type')
|
79
|
+
record = {
|
80
|
+
record_id: api_record.fetch('id'),
|
81
|
+
ttl: api_record.fetch('ttl'),
|
82
|
+
fqdn: api_record.fetch('name').present? ? "#{api_record.fetch('name')}.#{zone}" : zone,
|
83
|
+
}
|
84
|
+
|
85
|
+
return if record_type == 'SOA'
|
86
|
+
|
87
|
+
case record_type
|
88
|
+
when 'A', 'AAAA'
|
89
|
+
record.merge!(address: api_record.fetch('content'))
|
90
|
+
when 'ALIAS'
|
91
|
+
record.merge!(alias: api_record.fetch('content'))
|
92
|
+
when 'CNAME'
|
93
|
+
record.merge!(cname: api_record.fetch('content'))
|
94
|
+
when 'MX'
|
95
|
+
record.merge!(preference: api_record.fetch('prio'), exchange: api_record.fetch('content'))
|
96
|
+
when 'NS'
|
97
|
+
record.merge!(nsdname: api_record.fetch('content'))
|
98
|
+
when 'SPF', 'TXT'
|
99
|
+
record.merge!(txtdata: api_record.fetch('content'))
|
100
|
+
when 'SRV'
|
101
|
+
weight, port, host = api_record.fetch('content').split(' ')
|
102
|
+
|
103
|
+
record.merge!(
|
104
|
+
priority: api_record.fetch('prio'),
|
105
|
+
weight: weight,
|
106
|
+
port: port,
|
107
|
+
target: Record.ensure_ends_with_dot(host),
|
108
|
+
)
|
109
|
+
end
|
110
|
+
|
111
|
+
unless record.fetch(:fqdn).ends_with?('.')
|
112
|
+
record[:fqdn] += '.'
|
113
|
+
end
|
114
|
+
|
115
|
+
Record.const_get(record_type).new(record)
|
153
116
|
end
|
154
117
|
|
155
|
-
|
118
|
+
def api_hash(record, zone)
|
119
|
+
record_hash = {
|
120
|
+
name: record.fqdn.gsub("#{Record.ensure_ends_with_dot(zone)}", '').chomp('.'),
|
121
|
+
ttl: record.ttl,
|
122
|
+
type: record.type,
|
123
|
+
}
|
124
|
+
|
125
|
+
case record.type
|
126
|
+
when 'A', 'AAAA'
|
127
|
+
record_hash[:content] = record.address
|
128
|
+
when 'ALIAS'
|
129
|
+
record_hash[:content] = record.alias.chomp('.')
|
130
|
+
when 'CNAME'
|
131
|
+
record_hash[:content] = record.cname.chomp('.')
|
132
|
+
when 'MX'
|
133
|
+
record_hash[:prio] = record.preference
|
134
|
+
record_hash[:content] = record.exchange.chomp('.')
|
135
|
+
when 'NS'
|
136
|
+
record_hash[:content] = record.nsdname.chomp('.')
|
137
|
+
when 'SPF', 'TXT'
|
138
|
+
record_hash[:content] = record.txtdata
|
139
|
+
when 'SRV'
|
140
|
+
record_hash[:content] = "#{record.weight} #{record.port} #{record.target.chomp('.')}"
|
141
|
+
record_hash[:prio] = record.priority
|
142
|
+
end
|
143
|
+
|
144
|
+
record_hash
|
145
|
+
end
|
156
146
|
end
|
157
147
|
end
|
158
148
|
end
|
@@ -2,96 +2,98 @@ require 'fog/dynect'
|
|
2
2
|
|
3
3
|
module RecordStore
|
4
4
|
class Provider::DynECT < Provider
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
class << self
|
6
|
+
def freeze_zone(zone)
|
7
|
+
session.put_zone(zone, freeze: true)
|
8
|
+
end
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
|
10
|
+
def thaw_zone(zone)
|
11
|
+
session.put_zone(zone, thaw: true)
|
12
|
+
end
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
|
14
|
+
def add(record, zone)
|
15
|
+
session.post_record(record.type, zone, record.fqdn, record.rdata, ttl: record.ttl)
|
16
|
+
end
|
16
17
|
|
17
|
-
|
18
|
-
|
19
|
-
|
18
|
+
def remove(record, zone)
|
19
|
+
session.delete_record(record.type, zone, record.fqdn, record.id)
|
20
|
+
end
|
20
21
|
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
24
25
|
|
25
|
-
|
26
|
-
|
27
|
-
|
26
|
+
def publish(zone)
|
27
|
+
session.put_zone(zone, publish: true)
|
28
|
+
end
|
28
29
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
30
|
+
# Applies changeset to provider
|
31
|
+
def apply_changeset(changeset, stdout = $stdout)
|
32
|
+
begin
|
33
|
+
thaw_zone(changeset.zone)
|
34
|
+
super
|
35
|
+
publish(changeset.zone)
|
36
|
+
rescue StandardError
|
37
|
+
puts "An exception occurred while applying DNS changes, deleting changeset"
|
38
|
+
discard_change_set(changeset.zone)
|
39
|
+
raise
|
40
|
+
ensure
|
41
|
+
freeze_zone(changeset.zone)
|
42
|
+
end
|
41
43
|
end
|
42
|
-
end
|
43
44
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
45
|
+
# returns an array of Record objects that match the records which exist in the provider
|
46
|
+
def retrieve_current_records(zone:, stdout: $stdout)
|
47
|
+
session.get_all_records(zone).body.fetch('data').flat_map do |type, records|
|
48
|
+
records.map do |record_body|
|
49
|
+
begin
|
50
|
+
build_from_api(record_body)
|
51
|
+
rescue StandardError => e
|
52
|
+
stdout.puts "Cannot build record: #{record_body}"
|
53
|
+
end
|
52
54
|
end
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
55
|
+
end.select(&:present?)
|
56
|
+
end
|
56
57
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
58
|
+
# Returns an array of the zones managed by provider as strings
|
59
|
+
def zones
|
60
|
+
session.zones.map(&:domain)
|
61
|
+
end
|
61
62
|
|
62
|
-
|
63
|
+
private
|
63
64
|
|
64
|
-
|
65
|
-
|
66
|
-
|
65
|
+
def discard_change_set(zone)
|
66
|
+
session.request(expects: 200, method: :delete, path: "ZoneChanges/#{zone}")
|
67
|
+
end
|
67
68
|
|
68
|
-
|
69
|
-
|
70
|
-
|
69
|
+
def session
|
70
|
+
@dns ||= Fog::DNS.new(session_params)
|
71
|
+
end
|
71
72
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
73
|
+
def session_params
|
74
|
+
{
|
75
|
+
provider: 'Dynect',
|
76
|
+
dynect_customer: secrets.fetch('customer'),
|
77
|
+
dynect_username: secrets.fetch('username'),
|
78
|
+
dynect_password: secrets.fetch('password')
|
79
|
+
}
|
80
|
+
end
|
80
81
|
|
81
|
-
|
82
|
-
|
83
|
-
|
82
|
+
def secrets
|
83
|
+
super.fetch('dynect')
|
84
|
+
end
|
84
85
|
|
85
|
-
|
86
|
-
|
86
|
+
def build_from_api(api_record)
|
87
|
+
record = api_record.merge(api_record.fetch('rdata')).slice!('rdata').symbolize_keys
|
87
88
|
|
88
|
-
|
89
|
+
return if record.fetch(:record_type) == 'SOA'
|
89
90
|
|
90
|
-
|
91
|
-
|
92
|
-
|
91
|
+
unless record.fetch(:fqdn).ends_with?('.')
|
92
|
+
record[:fqdn] = "#{record.fetch(:fqdn)}."
|
93
|
+
end
|
93
94
|
|
94
|
-
|
95
|
+
Record.const_get(record.fetch(:record_type)).new(record)
|
96
|
+
end
|
95
97
|
end
|
96
98
|
end
|
97
99
|
end
|
data/lib/record_store/record.rb
CHANGED
@@ -19,6 +19,7 @@ module RecordStore
|
|
19
19
|
|
20
20
|
def self.build_from_yaml_definition(yaml_definition)
|
21
21
|
record_type = yaml_definition.fetch(:type)
|
22
|
+
|
22
23
|
# TODO: remove backward compatibility support for ALIAS records using cname attribute instead of alias
|
23
24
|
# REMOVE after merging https://github.com/Shopify/record-store/pull/781
|
24
25
|
case record_type
|