orats 0.5.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,107 +0,0 @@
1
- require 'orats/version'
2
- require 'orats/shell'
3
- require 'orats/foreman'
4
-
5
- module Orats
6
- class Command
7
- include Thor::Base
8
- include Thor::Shell
9
- include Thor::Actions
10
- #source_root Dir.pwd
11
-
12
- include Shell
13
- include Foreman
14
-
15
- attr_accessor :active_path
16
-
17
- def initialize(app_name = '', options = {})
18
- @app_name = app_name
19
- @options = options
20
-
21
- # required to mix in thor actions without having a base thor class
22
- #@destination_stack = [self.class.source_root]
23
- self.destination_root = Dir.pwd
24
- @behavior = :invoke
25
- end
26
-
27
- def new
28
- @active_path = @app_name
29
- @active_path = services_path(@app_name)
30
-
31
- rails_template 'base' do
32
- gsub_postgres_info
33
- git_commit 'Change the postgres information'
34
-
35
- unless @options[:redis_password].empty?
36
- gsub_redis_info
37
- git_commit 'Add the redis password'
38
- end
39
-
40
- gsub_project_path
41
- git_commit 'Add the development project path'
42
-
43
- bundle_install
44
- git_commit 'Add gem lock file'
45
-
46
- bundle_binstubs
47
- git_commit 'Add binstubs for the important gems'
48
-
49
- spring_binstub
50
- git_commit 'Springify all of the bins'
51
-
52
- run_rake 'db:create:all db:migrate'
53
- git_commit 'Add the database schema file'
54
- end
55
-
56
- if @options[:auth]
57
- rails_template 'auth', '--skip ' do
58
- run_rake 'db:migrate db:seed'
59
- end
60
- end
61
-
62
- unless @options[:skip_extras]
63
- ansible_init @app_name
64
- end
65
-
66
- @active_path = services_path(@app_name)
67
- foreman_init
68
- end
69
-
70
- def play
71
- play_app @app_name
72
- end
73
-
74
- def nuke
75
- @active_path = @app_name
76
-
77
- nuke_warning
78
-
79
- nuke_data_details_warning unless @options[:skip_data]
80
-
81
- confirmed_to_delete = yes?('Are you sure? (y/N)', :cyan)
82
-
83
- if confirmed_to_delete
84
- nuke_data unless @options[:skip_data]
85
-
86
- nuke_directory
87
- end
88
- end
89
-
90
- def outdated
91
- outdated_init
92
- end
93
-
94
- def version
95
- puts "Orats version #{VERSION}"
96
- end
97
-
98
- private
99
- def active_project
100
- File.basename @active_path
101
- end
102
-
103
- def services_path(app_name)
104
- @options[:skip_extras] ? app_name : "#{app_name}/services/#{active_project}"
105
- end
106
- end
107
- end
@@ -1,54 +0,0 @@
1
- require 'socket'
2
- require 'timeout'
3
-
4
- module Orats
5
- module Foreman
6
- def foreman_init
7
-
8
- @options[:skip_foreman_start] ? message = 'Start your' : message = 'Starting'
9
-
10
- puts '', '='*80
11
- say_status 'action', "\e[1m#{message} server with the following commands:\e[0m", :cyan
12
- say_status 'command', "cd #{@active_path}", :magenta
13
- say_status 'command', 'bundle exec foreman start', :magenta
14
- puts '='*80, ''
15
-
16
- attempt_to_start unless @options[:skip_foreman_start]
17
- end
18
-
19
- private
20
-
21
- def attempt_to_start
22
- while port_taken? do
23
- puts
24
- say_status 'error', "\e[1mAnother application is using port 3000\n\e[0m", :red
25
- puts '-'*80
26
-
27
- exit 1 if no?('Would you like to try running the server again? (y/N)', :cyan)
28
- end
29
-
30
- puts
31
-
32
- run_from @active_path, 'bundle exec foreman start'
33
- end
34
-
35
- def port_taken?
36
- begin
37
- Timeout::timeout(5) do
38
- begin
39
- s = TCPSocket.new('localhost', 3000)
40
- s.close
41
-
42
- return true
43
- rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
44
- return false
45
- end
46
- end
47
- rescue Timeout::Error
48
- false
49
- end
50
-
51
- false
52
- end
53
- end
54
- end
@@ -1,503 +0,0 @@
1
- require 'securerandom'
2
- require 'open-uri'
3
-
4
- module Orats
5
- module Shell
6
- def run_from(path, command)
7
- run "cd #{path} && #{command} && cd -"
8
- end
9
-
10
- def log_message(type, message)
11
- puts
12
- say_status type, "#{message}...", :yellow
13
- puts '-'*80, ''; sleep 0.25
14
- end
15
-
16
- def log_status(type, message, color)
17
- puts
18
- say_status type, set_color(message, :bold), color
19
- end
20
-
21
- def log_status_under(type, message, color)
22
- say_status type, message, color
23
- puts
24
- end
25
-
26
- def log_results(results, message)
27
- log_status 'results', results, :magenta
28
- log_status_under 'message', message, :white
29
- end
30
-
31
- def git_commit(message)
32
- run_from @active_path, "git add -A && git commit -m '#{message}'"
33
- end
34
-
35
- def gsub_postgres_info
36
- log_message 'root', 'Changing the postgres information'
37
-
38
- gsub_file "#{@active_path}/.env", 'DATABASE_HOST: localhost', "DATABASE_HOST: #{@options[:pg_location]}"
39
- gsub_file "#{@active_path}/.env", ': postgres', ": #{@options[:pg_username]}"
40
- gsub_file "#{@active_path}/.env", ': supersecrets', ": #{@options[:pg_password]}"
41
- end
42
-
43
- def gsub_redis_info
44
- log_message 'root', 'Adding the redis password'
45
-
46
- gsub_file "#{@active_path}/config/initializers/sidekiq.rb", '//', "//:#{ENV['CACHE_PASSWORD']}@"
47
- gsub_file "#{@active_path}/.env", 'HE_PASSWORD: ', "HE_PASSWORD: #{@options[:redis_password]}"
48
- gsub_file "#{@active_path}/.env", 'CACHE_HOST: localhost', "CACHE_HOST: #{@options[:redis_location]}"
49
- gsub_file "#{@active_path}/config/application.rb", '# pass', 'pass'
50
- end
51
-
52
- def gsub_project_path
53
- log_message 'root', 'Changing the project path'
54
-
55
- gsub_file "#{@active_path}/.env", ': /full/path/to/your/project', ": #{File.expand_path(@active_path)}"
56
- end
57
-
58
- def run_rake(command)
59
- log_message 'shell', 'Running rake commands'
60
-
61
- run_from @active_path, "bundle exec rake #{command}"
62
- end
63
-
64
- def bundle_install
65
- log_message 'shell', 'Running bundle install, this may take a while'
66
-
67
- run_from @active_path, 'bundle install'
68
- end
69
-
70
- def bundle_binstubs
71
- log_message 'shell', 'Running bundle binstubs for a few gems'
72
-
73
- run_from @active_path, 'bundle binstubs whenever puma sidekiq'
74
- end
75
-
76
- def spring_binstub
77
- log_message 'shell', 'Running spring binstub'
78
-
79
- run_from @active_path, 'bundle exec spring binstub --all'
80
- end
81
-
82
- def nuke_warning
83
- puts
84
- say_status 'nuke', "\e[1mYou are about to permanently delete this directory:\e[0m", :red
85
- say_status 'path', "#{File.expand_path(@app_name)}", :yellow
86
- puts
87
- end
88
-
89
- def rails_directories
90
- rails_gemfiles = run("find #{@active_path} -type f -name Gemfile | xargs grep -lE \"gem 'rails'|gem \\\"rails\\\"\"", capture: true)
91
- gemfile_paths = rails_gemfiles.split("\n")
92
-
93
- gemfile_paths.map { |gemfile| File.dirname(gemfile) }
94
- end
95
-
96
- def nuke_data_details_warning
97
- rails_projects = []
98
-
99
- rails_directories.each do |rails_dir|
100
- rails_projects << File.basename(rails_dir)
101
- end
102
-
103
- project_names = rails_projects.join(', ')
104
-
105
- puts
106
- say_status 'nuke', "\e[1mYou are about to permanently delete all postgres databases for:\e[0m", :red
107
- say_status 'databases', project_names, :yellow
108
- puts
109
- say_status 'nuke', "\e[1mYou are about to permanently delete all redis namespaces for:\e[0m", :red
110
- say_status 'namespace', project_names, :yellow
111
- puts
112
- end
113
-
114
- def nuke_data
115
- rails_directories.each do |directory|
116
- log_message 'root', 'Removing postgres databases'
117
- run_from directory, 'bundle exec rake db:drop:all'
118
- nuke_redis File.basename(directory)
119
- end
120
- end
121
-
122
- def can_play?
123
- log_message 'shell', 'Checking for the ansible binary'
124
-
125
- has_ansible = run('which ansible', capture: true)
126
-
127
- dependency_error 'Cannot access ansible',
128
- 'Are you sure you have ansible setup correctly?',
129
- 'http://docs.ansible.com/intro_installation.html`' if has_ansible.empty?
130
-
131
- !has_ansible.empty?
132
- end
133
-
134
- def rails_template(command, flags = '')
135
- exit_if_cannot_rails
136
- exit_if_exists unless flags.index(/--skip/)
137
-
138
- run "rails new #{@active_path} #{flags} --skip-bundle --template #{File.expand_path File.dirname(__FILE__)}/templates/#{command}.rb"
139
- yield if block_given?
140
- end
141
-
142
- def play_app(path)
143
- return unless can_play?
144
-
145
- @active_path = path
146
- rails_template 'play'
147
- end
148
-
149
- def ansible_init(path)
150
- log_message 'shell', 'Creating ansible inventory'
151
- run "mkdir #{path}/inventory"
152
- run "mkdir #{path}/inventory/group_vars"
153
- copy_from_includes 'inventory/hosts', path
154
- copy_from_includes 'inventory/group_vars/all.yml', path
155
-
156
- secrets_path = "#{path}/secrets"
157
- log_message 'shell', 'Creating ansible secrets'
158
- run "mkdir #{secrets_path}"
159
-
160
- save_secret_string "#{secrets_path}/postgres_password"
161
-
162
- if @options[:redis_password].empty?
163
- run "touch #{secrets_path}/redis_password"
164
- else
165
- save_secret_string "#{secrets_path}/redis_password"
166
- gsub_file "#{path}/inventory/group_vars/all.yml", 'redis_password: false', 'redis_password: true'
167
- end
168
-
169
- save_secret_string "#{secrets_path}/mail_password"
170
- save_secret_string "#{secrets_path}/rails_token"
171
- save_secret_string "#{secrets_path}/devise_token"
172
- save_secret_string "#{secrets_path}/devise_pepper_token"
173
-
174
- log_message 'shell', 'Modifying secrets path in group_vars/all.yml'
175
- gsub_file "#{path}/inventory/group_vars/all.yml", '~/tmp/testproj/secrets/', File.expand_path(secrets_path)
176
-
177
- log_message 'shell', 'Modifying the place holder app name in group_vars/all.yml'
178
- gsub_file "#{path}/inventory/group_vars/all.yml", 'testproj', File.basename(path)
179
- gsub_file "#{path}/inventory/group_vars/all.yml", 'TESTPROJ', File.basename(path).upcase
180
-
181
- log_message 'shell', 'Creating ssh keypair'
182
- run "ssh-keygen -t rsa -P '' -f #{secrets_path}/id_rsa"
183
-
184
- log_message 'shell', 'Creating self signed ssl certificates'
185
- run create_rsa_certificate(secrets_path, 'sslkey.key', 'sslcert.crt')
186
-
187
- log_message 'shell', 'Creating monit pem file'
188
- run "#{create_rsa_certificate(secrets_path, 'monit.pem', 'monit.pem')} && openssl gendh 512 >> #{secrets_path}/monit.pem"
189
-
190
- install_role_dependencies unless @options[:skip_galaxy]
191
- end
192
-
193
- def outdated_init
194
- latest_gem_version = compare_gem_version
195
-
196
- github_repo = "https://raw.githubusercontent.com/nickjj/orats/#{latest_gem_version}/lib/orats"
197
-
198
- galaxy_url = "#{github_repo}/templates/includes/Galaxyfile"
199
- playbook_url = "#{github_repo}/templates/play.rb"
200
- inventory_url = "#{github_repo}/templates/includes/inventory/group_vars/all.yml"
201
-
202
- remote_galaxy_contents = url_to_string(galaxy_url)
203
- remote_playbook_contents = url_to_string(playbook_url)
204
- remote_inventory_contents = url_to_string(inventory_url)
205
-
206
- compare_remote_role_version_to_local remote_galaxy_contents
207
-
208
- local_playbook = compare_remote_to_local('playbook',
209
- 'roles',
210
- playbook_file_stats(remote_playbook_contents),
211
- playbook_file_stats(IO.read(playbook_file_path)))
212
-
213
- local_inventory = compare_remote_to_local('inventory',
214
- 'variables',
215
- inventory_file_stats(remote_inventory_contents),
216
- inventory_file_stats(IO.read(inventory_file_path)))
217
-
218
- unless @options[:playbook].empty?
219
- compare_user_to_local('playbook', 'roles', @options[:playbook], local_playbook) do
220
- playbook_file_stats IO.read(@options[:playbook])
221
- end
222
- end
223
-
224
- unless @options[:inventory].empty?
225
- compare_user_to_local('inventory', 'variables', @options[:inventory], local_inventory) do
226
- inventory_file_stats IO.read(@options[:inventory])
227
- end
228
- end
229
- end
230
-
231
- private
232
-
233
- def inventory_file_stats(file)
234
- # pluck out all of the values contained with {{ }}
235
- ansible_variable_list = file.scan(/\{\{([^{{}}]*)\}\}/)
236
-
237
- # remove the leading space
238
- ansible_variable_list.map! { |line| line.first[0] = '' }
239
-
240
- # match every line that is not a comment and contains a colon
241
- inventory_variable_list = file.scan(/^[^#].*:/)
242
-
243
- inventory_variable_list.map! do |line|
244
- # only strip lines that need it
245
- line.strip! if line.include?(' ') || line.include?("\n")
246
-
247
- # get rid of the trailing colon
248
- line.chomp(':')
249
-
250
- # if a value of a certain variable has a colon then the regex
251
- # picks this up as a match. only take the variable name
252
- # if this happens to occur
253
- line.split(':').first if line.include?(':')
254
- end
255
-
256
- (ansible_variable_list + inventory_variable_list).uniq
257
- end
258
-
259
- def playbook_file_stats(file)
260
- # match every line that is not a comment and has a role defined
261
- roles_list = file.scan(/^.*role:.*/)
262
-
263
- roles_list.map! do |line|
264
- # only strip lines that need it
265
- line.strip! if line.include?(' ') || line.include?("\n")
266
-
267
- role_parts = line.split('role:')
268
-
269
- line = role_parts[1]
270
-
271
- if line.include?(',')
272
- line = line.split(',').first
273
- end
274
-
275
- line.strip! if line.include?(' ')
276
- end
277
-
278
- roles_list.reject! { |line| line.start_with?('#') }
279
-
280
- roles_list.uniq
281
- end
282
-
283
- def compare_gem_version
284
- latest_gem_contents = `gem list orats --remote`.split.last
285
-
286
- if latest_gem_contents.include?('ERROR')
287
- say_status 'error', "\e[1mError running `gem list orats --remote`:\e[0m", :red
288
- say_status 'msg', 'Chances are their API is down, try again soon', :yellow
289
- exit 1
290
- end
291
-
292
- latest_gem_version = "v#{latest_gem_contents.split.first[1...-1]}"
293
-
294
- log_status 'gem', 'Comparing this version of orats to the latest orats version:', :green
295
- log_status_under 'version', "Latest: #{latest_gem_version}, Yours: v#{VERSION}", :yellow
296
-
297
- latest_gem_version
298
- end
299
-
300
- def compare_remote_role_version_to_local(remote_galaxy_contents)
301
- remote_galaxy_list = remote_galaxy_contents.split
302
- local_galaxy_contents = IO.read(galaxy_file_path)
303
- local_galaxy_list = local_galaxy_contents.split
304
-
305
- galaxy_difference = remote_galaxy_list - local_galaxy_list
306
-
307
- local_role_count = local_galaxy_list.size
308
- different_roles = galaxy_difference.size
309
-
310
- log_status 'roles', "Comparing this version of orats' roles to the latest version:", :green
311
-
312
- if different_roles == 0
313
- log_status_under 'message', "All #{local_role_count} roles are up to date", :yellow
314
- else
315
- log_status_under 'message', "There are #{different_roles} differences", :yellow
316
-
317
- galaxy_difference.each do |role_line|
318
- name = role_line.split(',').first
319
- status = 'outdated'
320
- color = :yellow
321
-
322
- unless local_galaxy_contents.include?(name)
323
- status = 'missing'
324
- color = :red
325
- end
326
-
327
- say_status status, name, color
328
- end
329
-
330
- log_results 'The latest version of orats may benefit you:', 'Check github to see if the changes interest you'
331
- end
332
- end
333
-
334
- def compare_remote_to_local(label, keyword, remote_list, local_list)
335
- list_difference = remote_list - local_list
336
-
337
- remote_count = list_difference.size#log_unmatched remote_list, local_list, 'remote', :yellow
338
-
339
- log_status label, "Comparing this version of orats' #{label} to the latest version:", :green
340
- log_status_under 'file', label == 'playbook' ? 'site.yml' : 'all.yml', :yellow
341
-
342
- list_difference.each do |line|
343
- say_status 'missing', line, :red unless local_list.include?(line)
344
- end
345
-
346
- if remote_count > 0
347
- log_results "#{remote_count} new #{keyword} are available:", 'You may benefit from upgrading to the latest orats'
348
- else
349
- log_results 'Everything appears to be in order:', "No missing #{keyword} were found"
350
- end
351
-
352
- local_list
353
- end
354
-
355
- def compare_user_to_local(label, keyword, user_path, local_list)
356
- if File.exist?(user_path) && File.file?(user_path)
357
- user_list = yield
358
-
359
- just_file_name = user_path.split('/').last
360
-
361
- log_status label, "Comparing this version of orats' #{label} to #{just_file_name}:", :blue
362
- log_status_under 'path', user_path, :cyan
363
-
364
- missing_count = log_unmatched local_list, user_list, 'missing', :red
365
- extra_count = log_unmatched user_list, local_list, 'extra', :yellow
366
-
367
- if missing_count > 0
368
- log_results "#{missing_count} #{keyword} are missing:", "Your ansible run will likely fail with this #{label}"
369
- else
370
- log_results 'Everything appears to be in order:', "No missing #{keyword} were found"
371
- end
372
-
373
- if extra_count > 0
374
- log_results "#{extra_count} extra #{keyword} were detected:", "No problem but remember to add them to a future #{keyword}"
375
- else
376
- log_results "No extra #{keyword} were found:", "Extra #{keyword} are fine but you have none"
377
- end
378
- else
379
- log_status label, "Comparing this version of orats' #{label} to ???:", :blue
380
- puts
381
- say_status 'error', "\e[1mError comparing #{label}:\e[0m", :red
382
- say_status 'path', user_path, :yellow
383
- say_status 'help', 'Make sure you supply a file name', :white
384
- end
385
- end
386
-
387
- def url_to_string(url)
388
- begin
389
- file_contents = open(url).read
390
- rescue OpenURI::HTTPError => ex
391
- say_status 'error', "\e[1mError browsing #{url}:\e[0m", :red
392
- say_status 'msg', ex, :yellow
393
- exit 1
394
- end
395
-
396
- file_contents
397
- end
398
-
399
- def log_unmatched(compare, against, label, color)
400
- count = 0
401
-
402
- compare.each do |item|
403
- unless against.include?(item)
404
- say_status label, item, color
405
- count += 1
406
- end
407
- end
408
-
409
- count
410
- end
411
-
412
- def create_rsa_certificate(secrets_path, keyout, out)
413
- "openssl req -new -newkey rsa:2048 -days 365 -nodes -x509 -subj '/C=US/ST=Foo/L=Bar/O=Baz/CN=qux.com' -keyout #{secrets_path}/#{keyout} -out #{secrets_path}/#{out}"
414
- end
415
-
416
- def galaxy_file_path
417
- "#{File.expand_path File.dirname(__FILE__)}/templates/includes/Galaxyfile"
418
- end
419
-
420
- def inventory_file_path
421
- "#{File.expand_path File.dirname(__FILE__)}/templates/includes/inventory/group_vars/all.yml"
422
- end
423
-
424
- def playbook_file_path
425
- "#{File.expand_path File.dirname(__FILE__)}/templates/play.rb"
426
- end
427
-
428
- def install_role_dependencies
429
- log_message 'shell', 'Updating ansible roles from the galaxy'
430
-
431
- galaxy_install = "ansible-galaxy install -r #{galaxy_file_path} --force"
432
- galaxy_out = run(galaxy_install, capture: true)
433
-
434
- if galaxy_out.include?('you do not have permission')
435
- if @options[:sudo_password].empty?
436
- sudo_galaxy_command = 'sudo'
437
- else
438
- sudo_galaxy_command = "echo #{@options[:sudo_password]} | sudo -S"
439
- end
440
-
441
- run("#{sudo_galaxy_command} #{galaxy_install}")
442
- end
443
- end
444
-
445
- def save_secret_string(file)
446
- File.open(file, 'w+') { |f| f.write(SecureRandom.hex(64)) }
447
- end
448
-
449
- def copy_from_includes(file, destination_root_path)
450
- base_path = "#{File.expand_path File.dirname(__FILE__)}/templates/includes"
451
-
452
- log_message 'shell', "Creating #{file}"
453
- run "cp #{base_path}/#{file} #{destination_root_path}/#{file}"
454
- end
455
-
456
- def nuke_redis(namespace)
457
- log_message 'root', 'Removing redis keys'
458
-
459
- run "redis-cli KEYS '#{namespace}:*' | xargs --delim='\n' redis-cli DEL"
460
- end
461
-
462
- def nuke_directory
463
- log_message 'root', 'Deleting directory'
464
-
465
- run "rm -rf #{@active_path}"
466
- end
467
-
468
- def dependency_error(message, question, answer)
469
- puts
470
- say_status 'error', "\e[1m#{message}\e[0m", :red
471
- say_status 'question', question, :yellow
472
- say_status 'answer', answer, :cyan
473
- puts '-'*80
474
- puts
475
- end
476
-
477
- def exit_if_cannot_rails
478
- log_message 'shell', 'Checking for rails'
479
-
480
- has_rails = run('which rails', capture: true)
481
-
482
- dependency_error 'Cannot access rails',
483
- 'Are you sure you have rails setup correctly?',
484
- 'You can install it by running `gem install rails`' if has_rails.empty?
485
-
486
- exit 1 if has_rails.empty?
487
- end
488
-
489
- def exit_if_exists
490
- log_message 'shell', 'Checking if a file or directory already exists'
491
-
492
- if Dir.exist?(@active_path) || File.exist?(@active_path)
493
- puts
494
- say_status 'aborting', "\e[1mA file or directory already exists at this location:\e[0m", :red
495
- say_status 'location', @active_path, :yellow
496
- puts '-'*80
497
- puts
498
-
499
- exit 1
500
- end
501
- end
502
- end
503
- end