orats 0.4.10 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4f16e8fca3d493283f74cfb52c13272f805f356d
4
- data.tar.gz: a0227a92fb0322e35d923994005522fb1f8e1f4c
3
+ metadata.gz: 04ca394edc402343a74153e8b1b7a2f8f61eb4c4
4
+ data.tar.gz: 302ace76f892bc1d4e17e6c66a3ee5d02c749f45
5
5
  SHA512:
6
- metadata.gz: 703767326261e5ec820485f01257534a56f645ade04aaf4f23e88af8a668c60c336d41481029115880a1b7e9aa91de1511b79e7f8e6f5b8a00631767e72a2e6a
7
- data.tar.gz: f59fa3fa97cfd45ce987d07845d25f72d0f61abd2e5e4756dec1f7ef4dd9a83483f8d51ba19628985911b77cbe776c56e4da3c9df83c3a1714b3c402e658e29c
6
+ metadata.gz: 84ef1382a808fc7b08fbbe1bc99422e3560da9e198fb2fbf122b4b2671de61aece9b67c5f7cffd8fbfd00d483298d884dd3b895787d08c0dcedc4377601643a1
7
+ data.tar.gz: d06a4516543e945daa0c42fc8dc28d58f72b10f505b21efea7c985a2bcb3b479ff1c3ae669c93643a8e8968ef934371f5547a1ec9bc39d5a47c8baf1d530a511
data/README.md CHANGED
@@ -73,6 +73,9 @@ running `orats <command name> help` from your terminal. You can also type `orats
73
73
  - Project features:
74
74
  - Optionally takes: `--skip-extras [false]`
75
75
  - Optionally takes: `--skip-foreman-start [false]`
76
+ - Ansible features:
77
+ - Optionally takes: `--sudo-password []`
78
+ - Optionally takes: `--skip-galaxy [false]`
76
79
 
77
80
  - Create an ansible playbook
78
81
  - `orats play <PATH>`
@@ -81,6 +84,11 @@ running `orats <command name> help` from your terminal. You can also type `orats
81
84
  - `orats nuke <APP_PATH>`
82
85
  - Optionally takes: `--skip-data [false]`
83
86
 
87
+ - Detect whether or not orats, the playbook or inventory is outdated
88
+ - `orats outdated [options]`
89
+ - Optionally takes: `--playbook-file []`
90
+ - Optionally takes: `--inventory-file []`
91
+
84
92
  #### Why is it asking me for my development postgres password?
85
93
 
86
94
  In order to automate certain tasks such as running database migrations the script must be able to talk to your database.
@@ -89,6 +97,22 @@ location will be `localhost` and the username will be `postgres` so these values
89
97
 
90
98
  Remember, this is only your development postgres password. It will **never** ask for your production passwords.
91
99
 
100
+ #### Is the outdated detection guaranteed to be accurate?
101
+
102
+ The version comparisons can be fully trusted but when comparing a specific playbook or inventory file it's not really
103
+ possible to guarantee a valid comparison.
104
+
105
+ When passing in `--playbook-file` or `--inventory-file` it will look for certain keywords in the file. If it finds the
106
+ keyword then it will assume that keyword is working and up to date. Since you can edit these files freely there may be
107
+ cases where it reports a false positive.
108
+
109
+ It's better than nothing and it also doubles as an upgrade guide too if you wanted to add in new role lines to your
110
+ playbook file or paste in a few new variables in your inventory that exist in a newer version of orats that you planned
111
+ to update.
112
+
113
+ It will detect missing, outdated and extra keywords between your version of orats, your user generated files and the
114
+ latest version on github. Execute `orats help outdated` if you get confused.
115
+
92
116
  ## Base
93
117
 
94
118
  This is the starter template that every other template will append to. I feel like when I make a new project, 95% of the time
@@ -129,6 +153,11 @@ Everything has been added with proper git commits so you have a trail of changes
129
153
 
130
154
  `orats new myapp --pg-password <development postgres db password>`
131
155
 
156
+ Towards the end of the run you might get prompted for a sudo password if you have not skipped installing the ansible
157
+ roles from the galaxy. It will only try to use sudo if it fails with a permission error first.
158
+
159
+ You can also provide a `--sudo-password=foo` flag to set your password so orats can finish without any user input.
160
+
132
161
  #### What's with the services directory?
133
162
 
134
163
  It is just a naming convention that I like to apply, you can name it whatever you want later or remove it with a flag. My thought
@@ -255,9 +284,7 @@ check out each role then here's a link to their repos:
255
284
  - `nickjj.nginx` https://github.com/nickjj/ansible-nginx
256
285
  - `DavidWittman.redis` https://github.com/DavidWittman/ansible-redis
257
286
 
258
- You will need to install the roles onto your workstation before you can use them. You can do that by running this command:
259
-
260
- `ansible-galaxy install nickjj.user nickjj.security nickjj.postgres nickjj.ruby nickjj.nodejs nickjj.nginx nickjj.rails nickjj.whenever nickjj.pumacorn nickjj.sidekiq nickjj.monit DavidWittman.redis --force`
287
+ All of the above roles will get installed and updated whenever you generate a `new` orats application.
261
288
 
262
289
  ### Try it
263
290
 
@@ -11,6 +11,8 @@ module Orats
11
11
  option :auth, type: :boolean, default: false, aliases: '-a'
12
12
  option :skip_extras, type: :boolean, default: false, aliases: '-E'
13
13
  option :skip_foreman_start, type: :boolean, default: false, aliases: '-F'
14
+ option :sudo_password, default: ''
15
+ option :skip_galaxy, type: :boolean, default: false, aliases: '-G'
14
16
  desc 'new APP_PATH [options]', ''
15
17
  long_desc <<-D
16
18
  `orats new myapp --pg-password supersecret` will create a new rails project and it will also create an ansible inventory to go with it by default.
@@ -38,6 +40,12 @@ module Orats
38
40
  `--skip-extras` skip creating the services directory and ansible inventory/secrets [false]
39
41
 
40
42
  `--skip-foreman-start` skip automatically running puma and sidekiq [false]
43
+
44
+ Ansible features:
45
+
46
+ `--sudo-password` to install ansible roles from the galaxy to a path outside of your user privileges []
47
+
48
+ `--skip-galaxy` skip automatically installing roles from the galaxy [false]
41
49
  D
42
50
  def new(app_name)
43
51
  Command.new(app_name, options).new
@@ -64,6 +72,28 @@ module Orats
64
72
  Command.new(app_name, options).nuke
65
73
  end
66
74
 
75
+ option :playbook_file, default: ''
76
+ option :inventory_file, default: ''
77
+ desc 'outdated [options]', ''
78
+ long_desc <<-D
79
+ `orats outdated` will run various comparisons on your ansible files.
80
+
81
+ Help:
82
+
83
+ `The green/yellow labels` denote a remote check to compare the files contained in your version of orats to the latest files on github.
84
+
85
+ `The blue/cyan labels` denote a local check between the files contained in your version of orats to the files you have generated such as your own playbook or inventories.
86
+
87
+ Options:
88
+
89
+ `--playbook-file` to supply a playbook file for comparison []
90
+
91
+ `--inventory-file` to supply an inventory file for comparison []
92
+ D
93
+ def outdated
94
+ Command.new(nil, options).outdated
95
+ end
96
+
67
97
  desc 'version', ''
68
98
  long_desc <<-D
69
99
  `orats version` will print the current version.
@@ -74,6 +104,7 @@ module Orats
74
104
  map %w(-v --version) => :version
75
105
 
76
106
  private
107
+
77
108
  def invoked?
78
109
  caller_locations(0).any? { |backtrace| backtrace.label == 'invoke' }
79
110
  end
@@ -87,6 +87,10 @@ module Orats
87
87
  end
88
88
  end
89
89
 
90
+ def outdated
91
+ outdated_init
92
+ end
93
+
90
94
  def version
91
95
  puts "Orats version #{VERSION}"
92
96
  end
@@ -1,4 +1,5 @@
1
1
  require 'securerandom'
2
+ require 'open-uri'
2
3
 
3
4
  module Orats
4
5
  module Shell
@@ -12,6 +13,21 @@ module Orats
12
13
  puts '-'*80, ''; sleep 0.25
13
14
  end
14
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
+
15
31
  def git_commit(message)
16
32
  run_from @active_path, "git add . && git commit -m '#{message}'"
17
33
  end
@@ -166,14 +182,266 @@ module Orats
166
182
  run "ssh-keygen -t rsa -P '' -f #{secrets_path}/id_rsa"
167
183
 
168
184
  log_message 'shell', 'Creating self signed ssl certificates'
169
- run "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}/sslkey.key -out #{secrets_path}/sslcert.crt"
185
+ run create_rsa_certificate(secrets_path, 'sslkey.key', 'sslcert.crt')
170
186
 
171
187
  log_message 'shell', 'Creating monit pem file'
172
- run "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}/monit.pem -out #{secrets_path}/monit.pem && openssl gendh 512 >> #{secrets_path}/monit.pem"
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
+ github_repo = 'https://raw.githubusercontent.com/nickjj/orats/master/lib/orats'
195
+
196
+ version_url = "#{github_repo}/version.rb"
197
+ galaxy_url = "#{github_repo}/templates/includes/Galaxyfile"
198
+ playbook_url = "#{github_repo}/templates/play.rb"
199
+ inventory_url = "#{github_repo}/templates/includes/inventory/group_vars/all.yml"
200
+
201
+ remote_version_contents = url_to_string(version_url)
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_gem_version remote_version_contents
207
+
208
+ compare_remote_role_version_to_local remote_galaxy_contents
209
+
210
+ local_playbook = compare_remote_to_local('playbook',
211
+ 'roles',
212
+ playbook_file_stats(remote_playbook_contents),
213
+ playbook_file_stats(IO.read(playbook_file_path)))
214
+
215
+ local_inventory = compare_remote_to_local('inventory',
216
+ 'variables',
217
+ inventory_file_stats(remote_inventory_contents),
218
+ inventory_file_stats(IO.read(inventory_file_path)))
219
+
220
+ unless @options[:playbook_file].empty?
221
+ compare_user_to_local('playbook', 'roles', @options[:playbook_file], local_playbook) do
222
+ playbook_file_stats IO.read(@options[:playbook_file])
223
+ end
224
+ end
225
+
226
+ unless @options[:inventory_file].empty?
227
+ compare_user_to_local('inventory', 'variables', @options[:inventory_file], local_inventory) do
228
+ inventory_file_stats IO.read(@options[:inventory_file])
229
+ end
230
+ end
173
231
  end
174
232
 
175
233
  private
176
234
 
235
+ def inventory_file_stats(file)
236
+ # pluck out all of the values contained with {{ }}
237
+ ansible_variable_list = file.scan(/\{\{([^{{}}]*)\}\}/)
238
+
239
+ # remove the leading space
240
+ ansible_variable_list.map! { |line| line.first[0] = '' }
241
+
242
+ # match every line that is not a comment and contains a colon
243
+ inventory_variable_list = file.scan(/^[^#].*:/)
244
+
245
+ inventory_variable_list.map! do |line|
246
+ # only strip lines that need it
247
+ line.strip! if line.include?(' ') || line.include?("\n")
248
+
249
+ # get rid of the trailing colon
250
+ line.chomp(':')
251
+
252
+ # if a value of a certain variable has a colon then the regex
253
+ # picks this up as a match. only take the variable name
254
+ # if this happens to occur
255
+ line.split(':').first if line.include?(':')
256
+ end
257
+
258
+ (ansible_variable_list + inventory_variable_list).uniq
259
+ end
260
+
261
+ def playbook_file_stats(file)
262
+ # match every line that is not a comment and has a role defined
263
+ roles_list = file.scan(/^.*role:.*/)
264
+
265
+ roles_list.map! do |line|
266
+ # only strip lines that need it
267
+ line.strip! if line.include?(' ') || line.include?("\n")
268
+
269
+ role_parts = line.split('role:')
270
+
271
+ line = role_parts[1]
272
+
273
+ if line.include?(',')
274
+ line = line.split(',').first
275
+ end
276
+
277
+ line.strip! if line.include?(' ')
278
+ end
279
+
280
+ roles_list.reject! { |line| line.start_with?('#') }
281
+
282
+ roles_list.uniq
283
+ end
284
+
285
+ def compare_gem_version(latest_contents)
286
+ latest = latest_contents.match(/\'(.*)\'/)[1..-1].first
287
+
288
+ log_status 'gem', 'Comparing this version of orats to the latest orats version:', :green
289
+ log_status_under 'version', "Latest: v#{latest}, Yours: v#{VERSION}", :yellow
290
+ end
291
+
292
+ def compare_remote_role_version_to_local(remote_galaxy_contents)
293
+ remote_galaxy_list = remote_galaxy_contents.split
294
+ local_galaxy_contents = IO.read(galaxy_file_path)
295
+ local_galaxy_list = local_galaxy_contents.split
296
+
297
+ galaxy_difference = remote_galaxy_list - local_galaxy_list
298
+
299
+ local_role_count = local_galaxy_list.size
300
+ different_roles = galaxy_difference.size
301
+
302
+ log_status 'roles', "Comparing this version of orats' roles to the latest version:", :green
303
+
304
+ if different_roles == 0
305
+ log_status_under 'message', "All #{local_role_count} roles are up to date", :yellow
306
+ else
307
+ log_status_under 'message', "There are #{different_roles} differences", :yellow
308
+
309
+ galaxy_difference.each do |role_line|
310
+ name = role_line.split(',').first
311
+ status = 'outdated'
312
+ color = :yellow
313
+
314
+ unless local_galaxy_contents.include?(name)
315
+ status = 'missing'
316
+ color = :red
317
+ end
318
+
319
+ say_status status, name, color
320
+ end
321
+
322
+ log_results 'The latest version of orats may benefit you:', 'Check github to see if the changes interest you'
323
+ end
324
+ end
325
+
326
+ def compare_remote_to_local(label, keyword, remote_list, local_list)
327
+ list_difference = remote_list - local_list
328
+
329
+ remote_count = list_difference.size#log_unmatched remote_list, local_list, 'remote', :yellow
330
+
331
+ log_status label, "Comparing this version of orats' #{label} to the latest version:", :green
332
+ log_status_under 'file', label == 'playbook' ? 'site.yml' : 'all.yml', :yellow
333
+
334
+ list_difference.each do |line|
335
+ say_status 'missing', line, :red unless local_list.include?(line)
336
+ end
337
+
338
+ if remote_count > 0
339
+ log_results "#{remote_count} new #{keyword} are available:", 'You may benefit from upgrading to the latest orats'
340
+ else
341
+ log_results 'Everything appears to be in order:', "No missing #{keyword} were found"
342
+ end
343
+
344
+ local_list
345
+ end
346
+
347
+ def compare_user_to_local(label, keyword, user_path, local_list)
348
+ if File.exist?(user_path) && File.file?(user_path)
349
+ user_list = yield
350
+
351
+ just_file_name = user_path.split('/').last
352
+
353
+ log_status label, "Comparing this version of orats' #{label} to #{just_file_name}:", :blue
354
+ log_status_under 'path', user_path, :cyan
355
+
356
+ # really ugly hack to not count rails ENV variables as "missing" for each inventory that was generated
357
+ local_list = local_list.join("\n").gsub!('TESTPROJ_', '').split("\n") if label == 'inventory'
358
+
359
+ missing_count = log_unmatched local_list, user_list, 'missing', :red
360
+ extra_count = log_unmatched user_list, local_list, 'extra', :yellow
361
+
362
+ if missing_count > 0
363
+ log_results "#{missing_count} #{keyword} are missing:", "Your ansible run will likely fail with this #{label}"
364
+ else
365
+ log_results 'Everything appears to be in order:', "No missing #{keyword} were found"
366
+ end
367
+
368
+ if extra_count > 0
369
+ log_results "#{extra_count} extra #{keyword} were detected:", "No problem but remember to add them to a future #{keyword}"
370
+ else
371
+ log_results "No extra #{keyword} were found:", "Extra #{keyword} are fine but you have none"
372
+ end
373
+ else
374
+ log_status label, "Comparing this version of orats' #{label} to ???:", :blue
375
+ puts
376
+ say_status 'error', "\e[1mError comparing #{label}:\e[0m", :red
377
+ say_status 'path', user_path, :yellow
378
+ say_status 'help', 'Make sure you supply a file name', :white
379
+ end
380
+ end
381
+
382
+ def url_to_string(url)
383
+ begin
384
+ file_contents = open(url).read
385
+ rescue OpenURI::HTTPError => ex
386
+ say_status 'error', "\e[1mError browsing #{url}:\e[0m", :red
387
+ say_status 'msg', ex, :yellow
388
+ exit 1
389
+ end
390
+
391
+ file_contents
392
+ end
393
+
394
+ def log_unmatched(compare, against, label, color)
395
+ count = 0
396
+
397
+ against = against.join
398
+
399
+ compare.each do |item|
400
+ unless against.include?(item)
401
+ # really ugly hack to not count rails ENV variables as "extra" for each inventory that was generated
402
+ unless item == item.upcase && item.count('_') > 1
403
+ say_status label, item, color
404
+ count += 1
405
+ end
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
+
177
445
  def save_secret_string(file)
178
446
  File.open(file, 'w+') { |f| f.write(SecureRandom.hex(64)) }
179
447
  end
@@ -0,0 +1,12 @@
1
+ nickjj.user,v0.1.0
2
+ nickjj.security,v0.1.1
3
+ nickjj.postgres,v0.1.1
4
+ nickjj.ruby,v0.1.3
5
+ nickjj.nodejs,v0.1.1
6
+ nickjj.nginx,v0.1.3
7
+ nickjj.rails,v0.1.9
8
+ nickjj.whenever,v0.1.0
9
+ nickjj.pumacorn,v0.1.1
10
+ nickjj.sidekiq,v0.1.1
11
+ nickjj.monit,v0.1.2
12
+ DavidWittman.redis
@@ -1,3 +1,3 @@
1
1
  module Orats
2
- VERSION = '0.4.10'
2
+ VERSION = '0.5.0'
3
3
  end
@@ -65,6 +65,22 @@ class TestCLI < Minitest::Test
65
65
  assert_nuked app_name
66
66
  end
67
67
 
68
+ def test_outdated
69
+ app_name = generate_app_name
70
+
71
+ out, err = capture_subprocess_io do
72
+ orats "play #{app_name}"
73
+ end
74
+ assert_match /success/, out
75
+
76
+ out, err = capture_subprocess_io do
77
+ orats 'outdated'
78
+ end
79
+ assert_match /Comparing this version of/, out
80
+
81
+ assert_nuked app_name
82
+ end
83
+
68
84
  def test_version
69
85
  out, err = capture_subprocess_io do
70
86
  orats 'version'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: orats
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.10
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Janetakis
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-06-02 00:00:00.000000000 Z
11
+ date: 2014-06-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -89,6 +89,7 @@ files:
89
89
  - lib/orats/shell.rb
90
90
  - lib/orats/templates/auth.rb
91
91
  - lib/orats/templates/base.rb
92
+ - lib/orats/templates/includes/Galaxyfile
92
93
  - lib/orats/templates/includes/Gemfile
93
94
  - lib/orats/templates/includes/inventory/group_vars/all.yml
94
95
  - lib/orats/templates/includes/inventory/hosts