Class: RecordStore::CLI

Inherits:
Thor
  • Object
show all
Defined in:
lib/record_store/cli.rb

Constant Summary collapse

FORMATS =
%w[file directory]
SKIP_CHECKS =
'SKIP_DEPLOY_VALIDATIONS'

Instance Method Summary collapse

Constructor Details

#initialize(*args) ⇒ CLI

Returns a new instance of CLI.



6
7
8
9
# File 'lib/record_store/cli.rb', line 6

def initialize(*args)
  super
  RecordStore.config_path = options.fetch('config', "#{Dir.pwd}/config.yml")
end

Instance Method Details

#applyObject



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/record_store/cli.rb', line 95

def apply
  zones = Zone.modified

  if zones.empty?
    puts "No changes to sync"
    exit
  end

  zones.each do |zone|
    abort "Attempted to apply invalid zone: #{zone.name}" unless zone.valid?

    changesets = zone.build_changesets
    changesets.each(&:apply)
  end

  puts "All zone changes deployed"
end

#assert_empty_diffObject



179
180
181
182
183
184
185
# File 'lib/record_store/cli.rb', line 179

def assert_empty_diff
  zones = Zone.modified.map(&:name)

  unless zones.empty?
    abort "The following zones have diverged: #{zones.join(', ')}"
  end
end

#diffObject



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/record_store/cli.rb', line 41

def diff
  puts "Diffing #{Zone.defined.count} zones"

  Zone.each do |name, zone|
    changesets = zone.build_changesets

    if !options.fetch('verbose') && changesets.all?(&:empty?)
      print_and_flush('.')
      next
    else
      puts "\n"
      puts "Zone: #{name}"
    end

    changesets.each do |changeset|
      next if !options.fetch('verbose') && changeset.changes.empty?

      puts '-' * 20
      puts "Provider: #{changeset.provider.to_s}"

      if !changeset.additions.empty? || options.fetch('verbose')
        puts "Add:"
        changeset.additions.map(&:record).each do |record|
          puts " - #{record.to_s}"
        end
      end

      if !changeset.removals.empty? || options.fetch('verbose')
        puts "Remove:"
        changeset.removals.map(&:record).each do |record|
          puts " - #{record.to_s}"
        end
      end

      if !changeset.updates.empty? || options.fetch('verbose')
        puts "Update:"
        changeset.updates.map(&:record).each do |record|
          puts " - #{record.to_s}"
        end
      end

      if options.fetch('verbose')
        puts "Unchanged:"
        changeset.unchanged.each do |record|
          puts " - #{record.to_s}"
        end
      end
    end
    puts '=' * 20
  end
  puts "\n"
end

#downloadObject



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/record_store/cli.rb', line 116

def download
  name = options.fetch('name')
  abort 'Please omit the period at the end of the zone' if name.ends_with?('.')
  abort 'Zone with this name already exists in zones/' if File.exists?("#{RecordStore.zones_path}/#{name}.yml")

  provider = options.fetch('provider', Provider.provider_for(name))
  if provider.nil?
    puts "Could not find valid provider from #{name} SOA record"
    provider = ask("Please enter the provider in which #{name} exists")
  else
    puts "Identified #{provider} as the DNS provider"
  end

  puts "Downloading records for #{name}"
  Zone.download(name, provider)
  puts "Records have been downloaded & can be found in zones/#{name}.yml"
end

#freezeObject



21
22
23
24
25
26
27
# File 'lib/record_store/cli.rb', line 21

def freeze
  Zone.each do |_, zone|
    zone.providers.each do |provider|
      provider.freeze_zone(zone.unrooted_name) if provider.freezable?
    end
  end
end

#listObject



30
31
32
33
34
35
36
37
# File 'lib/record_store/cli.rb', line 30

def list
  Zone.each do |name, zone|
    puts "Zone: #{name}"
    zone.records.each do |record|
      record.log!
    end
  end
end

#reformatObject



136
137
138
139
140
141
142
143
144
# File 'lib/record_store/cli.rb', line 136

def reformat
  name = options['name']
  zones = name ? [Zone.find(name)] : Zone.all

  zones.each do |zone|
    puts "Writing #{zone.name}"
    zone.write
  end
end

#secretsObject



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/record_store/cli.rb', line 159

def secrets
  environment = begin
    if ENV['PRODUCTION']
      'production'
    elsif ENV['CI']
      'ci'
    else
      'dev'
    end
  end

  secrets = `ejson decrypt #{RecordStore.secrets_path.sub(/\.json\z/, ".#{environment}.ejson")}`
  if $?.exitstatus == 0
    File.write(RecordStore.secrets_path, secrets)
  else
    abort secrets
  end
end

#sortObject



148
149
150
151
152
153
154
155
156
# File 'lib/record_store/cli.rb', line 148

def sort
  name = options.fetch('name')
  abort "Please omit the period at the end of the zone" if name.ends_with?('.')

  yaml = YAML.load_file("#{RecordStore.zones_path}/#{name}.yml")
  yaml.fetch(name).fetch('records').sort_by! { |r| [r.fetch('fqdn'), r.fetch('type'), r['nsdname'] || r['address']] }

  File.write("#{RecordStore.zones_path}/#{name}.yml", yaml.deep_stringify_keys.to_yaml.gsub("---\n", ''))
end

#thawObject



12
13
14
15
16
17
18
# File 'lib/record_store/cli.rb', line 12

def thaw
  Zone.each do |_, zone|
    zone.providers.each do |provider|
      provider.thaw_zone(zone.unrooted_name) if provider.thawable?
    end
  end
end

#validate_all_presentObject



232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/record_store/cli.rb', line 232

def validate_all_present
  defined_zones = Set.new(RecordStore.defined_zones)
  expected_zones = Set.new(RecordStore.expected_zones)

  missing_zones = expected_zones - defined_zones
  extra_zones = defined_zones - expected_zones

  unless missing_zones.empty?
    abort "The following zones are missing: #{missing_zones.to_a.join(', ')}"
  end

  unless extra_zones.empty?
    abort "The following unexpected zones are defined: #{extra_zones.to_a.join(', ')}"
  end
end

#validate_change_sizeObject



217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/record_store/cli.rb', line 217

def validate_change_size
  zones = Zone.modified

  unless zones.empty?
    removals = zones.select do |zone|
      zone.changeset.removals.size > MAXIMUM_REMOVALS
    end

    unless removals.empty?
      abort "As a safety measure, you cannot remove more than #{MAXIMUM_REMOVALS} records at a time per zone. (zones failing this: #{removals.map(&:name).join(', ')})"
    end
  end
end

#validate_initial_stateObject



250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
# File 'lib/record_store/cli.rb', line 250

def validate_initial_state
  begin
    assert_empty_diff
    puts "Deploy will cause no changes, no need to validate initial state"
  rescue SystemExit
    if File.exists?(File.expand_path(SKIP_CHECKS, Dir.pwd))
      puts "Found '#{SKIP_CHECKS}', skipping predeploy validations"
    else
      puts "Checkout git SHA #{ENV['LAST_DEPLOYED_SHA']}"
      `git checkout #{ENV['LAST_DEPLOYED_SHA']}`
      abort "Checkout of old commit failed" if $?.exitstatus != 0

      `record-store assert_empty_diff`
      abort "Dyn status has diverged!" if $?.exitstatus != 0

      puts "Checkout git SHA #{ENV['REVISION']}"
      `git checkout #{ENV['REVISION']}`
      abort "Checkout of new commit failed" if $?.exitstatus != 0
    end
  end
end

#validate_recordsObject



188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/record_store/cli.rb', line 188

def validate_records
  invalid_zones = []
  Zone.all.reject(&:valid?).each do |zone|
    invalid_zones << zone.unrooted_name

    puts "#{zone.unrooted_name} definition is not valid:"
    zone.errors.each do |field, msg|
      puts " - #{field}: #{msg}"
    end

    invalid_records = zone.records.reject(&:valid?)
    puts '  Invalid records' if invalid_records.size > 0

    invalid_records.each do |record|
      puts "    #{record.to_s}"
      record.errors.each do |field, msg|
        puts "      - #{field}: #{msg}"
      end
    end
  end

  if invalid_zones.size > 0
    abort "The following zones were invalid: #{invalid_zones.join(', ')}"
  else
    puts "All zones have valid definitions."
  end
end