record_store 5.5.3 → 5.5.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop-https---shopify-github-io-ruby-style-guide-rubocop-yml +1027 -0
- data/.rubocop.yml +19 -0
- data/.travis.yml +1 -0
- data/bin/console +2 -2
- data/bin/record-store +1 -1
- data/bin/rubocop +29 -0
- data/dev.yml +4 -0
- data/lib/record_store.rb +4 -1
- data/lib/record_store/changeset.rb +8 -4
- data/lib/record_store/cli.rb +50 -48
- data/lib/record_store/provider.rb +11 -13
- data/lib/record_store/provider/dnsimple.rb +1 -2
- data/lib/record_store/provider/dynect.rb +6 -6
- data/lib/record_store/provider/google_cloud_dns.rb +5 -6
- data/lib/record_store/provider/ns1.rb +13 -9
- data/lib/record_store/provider/ns1/client.rb +3 -4
- data/lib/record_store/record.rb +27 -9
- data/lib/record_store/record/a.rb +4 -6
- data/lib/record_store/record/aaaa.rb +4 -6
- data/lib/record_store/record/alias.rb +5 -1
- data/lib/record_store/record/caa.rb +7 -3
- data/lib/record_store/record/cname.rb +5 -1
- data/lib/record_store/record/mx.rb +11 -3
- data/lib/record_store/record/ns.rb +5 -1
- data/lib/record_store/record/srv.rb +20 -4
- data/lib/record_store/record/txt.rb +1 -1
- data/lib/record_store/version.rb +1 -1
- data/lib/record_store/zone.rb +22 -16
- data/lib/record_store/zone/config.rb +1 -1
- data/lib/record_store/zone/yaml_definitions.rb +3 -2
- data/record_store.gemspec +4 -3
- data/template/bin/record-store +1 -1
- metadata +19 -2
data/.rubocop.yml
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
inherit_from:
|
2
|
+
- https://shopify.github.io/ruby-style-guide/rubocop.yml
|
3
|
+
|
4
|
+
AllCops:
|
5
|
+
TargetRubyVersion: 2.4
|
6
|
+
Exclude:
|
7
|
+
- vendor/**/*
|
8
|
+
UseCache: true
|
9
|
+
CacheRootDirectory: tmp/rubocop
|
10
|
+
|
11
|
+
Style/FrozenStringLiteralComment:
|
12
|
+
Enabled: false
|
13
|
+
|
14
|
+
Style/MethodCallWithArgsParentheses:
|
15
|
+
Include:
|
16
|
+
- '**/*.rb'
|
17
|
+
|
18
|
+
Style/ClassAndModuleChildren:
|
19
|
+
Enabled: false
|
data/.travis.yml
CHANGED
data/bin/console
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
|
3
|
+
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
4
4
|
require 'bundler/setup'
|
5
5
|
require 'record_store'
|
6
6
|
|
@@ -8,4 +8,4 @@ RecordStore.zones_path = File.expand_path('../../dev/zones', __FILE__)
|
|
8
8
|
RecordStore.config_path = File.expand_path('../../dev/config.yml', __FILE__)
|
9
9
|
|
10
10
|
require 'pry'
|
11
|
-
binding.pry(RecordStore)
|
11
|
+
binding.pry(RecordStore) # rubocop:disable Lint/Debugger
|
data/bin/record-store
CHANGED
data/bin/rubocop
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'rubocop' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("rubocop", "rubocop")
|
data/dev.yml
CHANGED
data/lib/record_store.rb
CHANGED
@@ -46,7 +46,10 @@ module RecordStore
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def zones_path
|
49
|
-
@zones_path ||= Pathname.new(
|
49
|
+
@zones_path ||= Pathname.new(
|
50
|
+
File.expand_path(config.fetch('zones_path'),
|
51
|
+
File.dirname(config_path)),
|
52
|
+
).realpath.to_s
|
50
53
|
end
|
51
54
|
|
52
55
|
def config_path
|
@@ -4,7 +4,9 @@ module RecordStore
|
|
4
4
|
attr_accessor :type, :record, :id
|
5
5
|
|
6
6
|
def initialize(type: nil, record: nil, id: nil)
|
7
|
-
@type
|
7
|
+
@type = type
|
8
|
+
@record = record
|
9
|
+
@id = id
|
8
10
|
end
|
9
11
|
|
10
12
|
def self.addition(record)
|
@@ -37,7 +39,7 @@ module RecordStore
|
|
37
39
|
def self.build_from(provider:, zone:)
|
38
40
|
current_zone = provider.build_zone(zone_name: zone.unrooted_name, config: zone.config)
|
39
41
|
|
40
|
-
|
42
|
+
new(
|
41
43
|
current_records: current_zone.records,
|
42
44
|
desired_records: zone.records,
|
43
45
|
provider: provider,
|
@@ -51,7 +53,9 @@ module RecordStore
|
|
51
53
|
@provider = provider
|
52
54
|
@zone = zone
|
53
55
|
|
54
|
-
@additions
|
56
|
+
@additions = []
|
57
|
+
@removals = []
|
58
|
+
@updates = []
|
55
59
|
|
56
60
|
build_changeset
|
57
61
|
end
|
@@ -81,7 +85,7 @@ module RecordStore
|
|
81
85
|
def build_changeset
|
82
86
|
current_records_set = (current_records - unchanged).sort_by(&:to_s).group_by(&:key)
|
83
87
|
desired_records_set = (desired_records - unchanged).sort_by(&:to_s).group_by(&:key)
|
84
|
-
current_records_set.default_proc = desired_records_set.default_proc =
|
88
|
+
current_records_set.default_proc = desired_records_set.default_proc = proc { [] }
|
85
89
|
|
86
90
|
record_keys = current_records_set.keys | desired_records_set.keys
|
87
91
|
|
data/lib/record_store/cli.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'English'
|
2
|
+
|
1
3
|
module RecordStore
|
2
4
|
class CLI < Thor
|
3
5
|
class_option :config, desc: 'Path to config.yml', aliases: '-c'
|
@@ -34,9 +36,7 @@ module RecordStore
|
|
34
36
|
def list
|
35
37
|
Zone.each do |name, zone|
|
36
38
|
puts "Zone: #{name}"
|
37
|
-
zone.records.each
|
38
|
-
record.log!
|
39
|
-
end
|
39
|
+
zone.records.each(&:log!)
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
@@ -60,34 +60,33 @@ module RecordStore
|
|
60
60
|
next if !options.fetch('verbose') && changeset.changes.empty?
|
61
61
|
|
62
62
|
puts '-' * 20
|
63
|
-
puts "Provider: #{changeset.provider
|
63
|
+
puts "Provider: #{changeset.provider}"
|
64
64
|
|
65
65
|
if !changeset.additions.empty? || options.fetch('verbose')
|
66
66
|
puts "Add:"
|
67
67
|
changeset.additions.map(&:record).each do |record|
|
68
|
-
puts " - #{record
|
68
|
+
puts " - #{record}"
|
69
69
|
end
|
70
70
|
end
|
71
71
|
|
72
72
|
if !changeset.removals.empty? || options.fetch('verbose')
|
73
73
|
puts "Remove:"
|
74
74
|
changeset.removals.map(&:record).each do |record|
|
75
|
-
puts " - #{record
|
75
|
+
puts " - #{record}"
|
76
76
|
end
|
77
77
|
end
|
78
78
|
|
79
79
|
if !changeset.updates.empty? || options.fetch('verbose')
|
80
80
|
puts "Update:"
|
81
81
|
changeset.updates.map(&:record).each do |record|
|
82
|
-
puts " - #{record
|
82
|
+
puts " - #{record}"
|
83
83
|
end
|
84
84
|
end
|
85
85
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
end
|
86
|
+
next unless options.fetch('verbose')
|
87
|
+
puts "Unchanged:"
|
88
|
+
changeset.unchanged.each do |record|
|
89
|
+
puts " - #{record}"
|
91
90
|
end
|
92
91
|
end
|
93
92
|
puts '=' * 20
|
@@ -105,7 +104,7 @@ module RecordStore
|
|
105
104
|
end
|
106
105
|
|
107
106
|
zones.each do |zone|
|
108
|
-
abort
|
107
|
+
abort("Attempted to apply invalid zone: #{zone.name}") unless zone.valid?
|
109
108
|
|
110
109
|
changesets = zone.build_changesets
|
111
110
|
changesets.each(&:apply)
|
@@ -116,11 +115,12 @@ module RecordStore
|
|
116
115
|
|
117
116
|
option :name, desc: 'Zone to download', aliases: '-n', type: :string, required: true
|
118
117
|
option :provider, desc: 'Provider in which this zone exists', aliases: '-p', type: :string
|
119
|
-
desc 'download', 'Downloads all records from zone and creates YAML zone definition in zones/
|
118
|
+
desc 'download', 'Downloads all records from zone and creates YAML zone definition in zones/ '\
|
119
|
+
'e.g. record-store download --name=shopify.io'
|
120
120
|
def download
|
121
121
|
name = options.fetch('name')
|
122
|
-
abort
|
123
|
-
abort
|
122
|
+
abort('Please omit the period at the end of the zone') if name.ends_with?('.')
|
123
|
+
abort('Zone with this name already exists in zones/') if File.exist?("#{RecordStore.zones_path}/#{name}.yml")
|
124
124
|
|
125
125
|
provider = options.fetch('provider', Provider.provider_for(name))
|
126
126
|
if provider.nil?
|
@@ -151,11 +151,12 @@ module RecordStore
|
|
151
151
|
desc 'sort', 'Sorts the zonefile alphabetically e.g. record-store sort --name=shopify.io'
|
152
152
|
def sort
|
153
153
|
name = options.fetch('name')
|
154
|
-
abort
|
154
|
+
abort("Please omit the period at the end of the zone") if name.ends_with?('.')
|
155
155
|
|
156
156
|
yaml = YAML.load_file("#{RecordStore.zones_path}/#{name}.yml")
|
157
|
-
yaml.fetch(name).fetch('records').sort_by!
|
158
|
-
|
157
|
+
yaml.fetch(name).fetch('records').sort_by! do |r|
|
158
|
+
[r.fetch('fqdn'), r.fetch('type'), r['nsdname'] || r['address']]
|
159
|
+
end
|
159
160
|
File.write("#{RecordStore.zones_path}/#{name}.yml", yaml.deep_stringify_keys.to_yaml.gsub("---\n", ''))
|
160
161
|
end
|
161
162
|
|
@@ -171,11 +172,11 @@ module RecordStore
|
|
171
172
|
end
|
172
173
|
end
|
173
174
|
|
174
|
-
secrets =
|
175
|
-
if
|
175
|
+
secrets = %x(ejson decrypt #{RecordStore.secrets_path.sub(/\.json\z/, ".#{environment}.ejson")})
|
176
|
+
if $CHILD_STATUS.success?
|
176
177
|
File.write(RecordStore.secrets_path, secrets)
|
177
178
|
else
|
178
|
-
abort
|
179
|
+
abort(secrets)
|
179
180
|
end
|
180
181
|
end
|
181
182
|
|
@@ -184,7 +185,7 @@ module RecordStore
|
|
184
185
|
zones = Zone.modified.map(&:name)
|
185
186
|
|
186
187
|
unless zones.empty?
|
187
|
-
abort
|
188
|
+
abort("The following zones have diverged: #{zones.join(', ')}")
|
188
189
|
end
|
189
190
|
end
|
190
191
|
|
@@ -200,18 +201,18 @@ module RecordStore
|
|
200
201
|
end
|
201
202
|
|
202
203
|
invalid_records = zone.records.reject(&:valid?)
|
203
|
-
puts ' Invalid records'
|
204
|
+
puts ' Invalid records' unless invalid_records.empty?
|
204
205
|
|
205
206
|
invalid_records.each do |record|
|
206
|
-
puts " #{record
|
207
|
+
puts " #{record}"
|
207
208
|
record.errors.each do |field, msg|
|
208
209
|
puts " - #{field}: #{msg}"
|
209
210
|
end
|
210
211
|
end
|
211
212
|
end
|
212
213
|
|
213
|
-
if invalid_zones.
|
214
|
-
abort
|
214
|
+
if invalid_zones.present?
|
215
|
+
abort("The following zones were invalid: #{invalid_zones.join(', ')}")
|
215
216
|
else
|
216
217
|
puts "All zones have valid definitions."
|
217
218
|
end
|
@@ -227,7 +228,10 @@ module RecordStore
|
|
227
228
|
end
|
228
229
|
|
229
230
|
unless removals.empty?
|
230
|
-
|
231
|
+
error = +"As a safety measure, you cannot remove more than #{MAXIMUM_REMOVALS} "
|
232
|
+
error << 'records at a time per zone. '
|
233
|
+
error << "(zones failing this: #{removals.map(&:name).join(', ')})"
|
234
|
+
abort(error)
|
231
235
|
end
|
232
236
|
end
|
233
237
|
end
|
@@ -235,30 +239,28 @@ module RecordStore
|
|
235
239
|
SKIP_CHECKS = 'SKIP_DEPLOY_VALIDATIONS'
|
236
240
|
desc 'validate_initial_state', "Validates state hasn't diverged since the last deploy"
|
237
241
|
def validate_initial_state
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
abort "Checkout of old commit failed" if $?.exitstatus != 0
|
242
|
+
assert_empty_diff
|
243
|
+
puts "Deploy will cause no changes, no need to validate initial state"
|
244
|
+
rescue SystemExit
|
245
|
+
if File.exist?(File.expand_path(SKIP_CHECKS, Dir.pwd))
|
246
|
+
puts "Found '#{SKIP_CHECKS}', skipping predeploy validations"
|
247
|
+
else
|
248
|
+
puts "Checkout git SHA #{ENV['LAST_DEPLOYED_SHA']}"
|
249
|
+
%x(git checkout #{ENV['LAST_DEPLOYED_SHA']})
|
250
|
+
abort("Checkout of old commit failed") unless $CHILD_STATUS.success?
|
248
251
|
|
249
|
-
|
250
|
-
|
252
|
+
%x(record-store secrets)
|
253
|
+
abort("Decrypt secrets failed") unless $CHILD_STATUS.success?
|
251
254
|
|
252
|
-
|
253
|
-
|
255
|
+
%x(record-store assert_empty_diff)
|
256
|
+
abort("Dyn status has diverged!") unless $CHILD_STATUS.success?
|
254
257
|
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
+
puts "Checkout git SHA #{ENV['REVISION']}"
|
259
|
+
%x(git checkout #{ENV['REVISION']})
|
260
|
+
abort("Checkout of new commit failed") unless $CHILD_STATUS.success?
|
258
261
|
|
259
|
-
|
260
|
-
|
261
|
-
end
|
262
|
+
%x(record-store secrets)
|
263
|
+
abort("Decrypt secrets failed") unless $CHILD_STATUS.success?
|
262
264
|
end
|
263
265
|
end
|
264
266
|
|
@@ -9,20 +9,18 @@ module RecordStore
|
|
9
9
|
begin
|
10
10
|
ns_server = dns.getresource(zone_name, Resolv::DNS::Resource::IN::SOA).mname.to_s
|
11
11
|
rescue Resolv::ResolvError
|
12
|
-
abort
|
12
|
+
abort("Domain doesn't exist")
|
13
13
|
end
|
14
14
|
|
15
15
|
case ns_server
|
16
|
-
when
|
16
|
+
when /\.dnsimple\.com\z/
|
17
17
|
'DNSimple'
|
18
|
-
when
|
18
|
+
when /\.dynect\.net\z/
|
19
19
|
'DynECT'
|
20
|
-
when
|
20
|
+
when /\.googledomains\.com\z/
|
21
21
|
'GoogleCloudDNS'
|
22
22
|
when /\.nsone\.net\z/
|
23
23
|
'NS1'
|
24
|
-
else
|
25
|
-
nil
|
26
24
|
end
|
27
25
|
end
|
28
26
|
|
@@ -54,7 +52,7 @@ module RecordStore
|
|
54
52
|
end
|
55
53
|
|
56
54
|
# returns an array of Record objects that match the records which exist in the provider
|
57
|
-
def retrieve_current_records(zone:, stdout: $stdout)
|
55
|
+
def retrieve_current_records(zone:, stdout: $stdout) # rubocop:disable Lint/UnusedMethodArgument
|
58
56
|
raise NotImplementedError
|
59
57
|
end
|
60
58
|
|
@@ -91,28 +89,28 @@ module RecordStore
|
|
91
89
|
end
|
92
90
|
|
93
91
|
def thawable?
|
94
|
-
|
92
|
+
respond_to?(:thaw_zone)
|
95
93
|
end
|
96
94
|
|
97
95
|
def freezable?
|
98
|
-
|
96
|
+
respond_to?(:freeze_zone)
|
99
97
|
end
|
100
98
|
|
101
99
|
def to_s
|
102
|
-
|
100
|
+
name.demodulize
|
103
101
|
end
|
104
102
|
|
105
103
|
private
|
106
104
|
|
107
|
-
def add(record)
|
105
|
+
def add(record) # rubocop:disable Lint/UnusedMethodArgument
|
108
106
|
raise NotImplementedError
|
109
107
|
end
|
110
108
|
|
111
|
-
def remove(record)
|
109
|
+
def remove(record) # rubocop:disable Lint/UnusedMethodArgument
|
112
110
|
raise NotImplementedError
|
113
111
|
end
|
114
112
|
|
115
|
-
def update(id, record)
|
113
|
+
def update(id, record) # rubocop:disable Lint/UnusedMethodArgument
|
116
114
|
raise NotImplementedError
|
117
115
|
end
|
118
116
|
end
|
@@ -113,7 +113,7 @@ module RecordStore
|
|
113
113
|
|
114
114
|
def api_hash(record, zone)
|
115
115
|
record_hash = {
|
116
|
-
name: record.fqdn.gsub(
|
116
|
+
name: record.fqdn.gsub(Record.ensure_ends_with_dot(zone).to_s, '').chomp('.'),
|
117
117
|
ttl: record.ttl,
|
118
118
|
type: record.type,
|
119
119
|
}
|
@@ -138,7 +138,6 @@ module RecordStore
|
|
138
138
|
record_hash[:content] = "#{record.weight} #{record.port} #{record.target.chomp('.')}"
|
139
139
|
record_hash[:priority] = record.priority
|
140
140
|
end
|
141
|
-
|
142
141
|
record_hash
|
143
142
|
end
|
144
143
|
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
require 'fog/dynect'
|
2
2
|
require 'limiter'
|
3
3
|
|
4
|
-
Fog::DNS::Dynect::Real.extend
|
5
|
-
Fog::DNS::Dynect::Real.limit_method
|
4
|
+
Fog::DNS::Dynect::Real.extend(Limiter::Mixin)
|
5
|
+
Fog::DNS::Dynect::Real.limit_method(:request, rate: 5, interval: 1) # 5 RPS == 300 RPM
|
6
6
|
|
7
7
|
module RecordStore
|
8
8
|
class Provider::DynECT < Provider
|
@@ -34,7 +34,7 @@ module RecordStore
|
|
34
34
|
|
35
35
|
# returns an array of Record objects that match the records which exist in the provider
|
36
36
|
def retrieve_current_records(zone:, stdout: $stdout)
|
37
|
-
session.get_all_records(zone).body.fetch('data').flat_map do |
|
37
|
+
session.get_all_records(zone).body.fetch('data').flat_map do |_type, records|
|
38
38
|
records.map do |record_body|
|
39
39
|
begin
|
40
40
|
build_from_api(record_body)
|
@@ -88,8 +88,8 @@ module RecordStore
|
|
88
88
|
|
89
89
|
def api_rdata(record)
|
90
90
|
case record.type
|
91
|
-
when 'TXT'
|
92
|
-
{ txtdata: record.rdata_txt }
|
91
|
+
when 'SPF', 'TXT'
|
92
|
+
{ txtdata: Record.long_quote(record.rdata_txt) }
|
93
93
|
else
|
94
94
|
record.rdata
|
95
95
|
end
|
@@ -102,7 +102,7 @@ module RecordStore
|
|
102
102
|
type = record.fetch(:record_type)
|
103
103
|
return if type == 'SOA'
|
104
104
|
|
105
|
-
record[:txtdata] = Record.unescape(record[:txtdata]) if %w[SPF TXT].include?(type)
|
105
|
+
record[:txtdata] = Record.unlong_quote(Record.unescape(record[:txtdata])) if %w[SPF TXT].include?(type)
|
106
106
|
|
107
107
|
fqdn = record.fetch(:fqdn)
|
108
108
|
fqdn = "#{fqdn}." unless fqdn.ends_with?('.')
|