orats 0.7.3 → 0.8.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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/Gemfile +1 -1
  4. data/{LICENSE.txt → LICENSE} +21 -21
  5. data/README.md +51 -346
  6. data/Rakefile +1 -1
  7. data/bin/orats +5 -2
  8. data/lib/orats/argv_adjust.rb +61 -0
  9. data/lib/orats/cli.rb +53 -141
  10. data/lib/orats/cli_help/new +27 -0
  11. data/lib/orats/cli_help/nuke +19 -0
  12. data/lib/orats/commands/new/exec.rb +59 -0
  13. data/lib/orats/commands/new/rails.rb +197 -0
  14. data/lib/orats/commands/new/server.rb +67 -0
  15. data/lib/orats/commands/nuke.rb +66 -44
  16. data/lib/orats/common.rb +76 -0
  17. data/lib/orats/postgres.rb +90 -0
  18. data/lib/orats/process.rb +35 -0
  19. data/lib/orats/redis.rb +25 -0
  20. data/lib/orats/shell.rb +12 -0
  21. data/lib/orats/templates/auth.rb +96 -82
  22. data/lib/orats/templates/base.rb +115 -110
  23. data/lib/orats/templates/includes/new/rails/.env +28 -28
  24. data/lib/orats/templates/includes/new/rails/Gemfile +4 -4
  25. data/lib/orats/templates/includes/new/rails/config/{whenever.rb → schedule.rb} +0 -0
  26. data/lib/orats/ui.rb +33 -0
  27. data/lib/orats/version.rb +3 -2
  28. data/lib/orats.rb +2 -1
  29. data/orats.gemspec +7 -5
  30. data/test/integration/cli_test.rb +28 -177
  31. data/test/test_helper.rb +24 -9
  32. metadata +17 -29
  33. data/lib/orats/commands/common.rb +0 -146
  34. data/lib/orats/commands/diff/compare.rb +0 -106
  35. data/lib/orats/commands/diff/exec.rb +0 -60
  36. data/lib/orats/commands/diff/parse.rb +0 -66
  37. data/lib/orats/commands/inventory.rb +0 -100
  38. data/lib/orats/commands/playbook.rb +0 -60
  39. data/lib/orats/commands/project/exec.rb +0 -74
  40. data/lib/orats/commands/project/rails.rb +0 -162
  41. data/lib/orats/commands/project/server.rb +0 -57
  42. data/lib/orats/commands/role.rb +0 -70
  43. data/lib/orats/commands/ui.rb +0 -47
  44. data/lib/orats/templates/includes/inventory/group_vars/all.yml +0 -202
  45. data/lib/orats/templates/includes/inventory/hosts +0 -8
  46. data/lib/orats/templates/includes/playbook/Galaxyfile +0 -15
  47. data/lib/orats/templates/includes/playbook/common.yml +0 -23
  48. data/lib/orats/templates/includes/playbook/site.yml +0 -36
  49. data/lib/orats/templates/includes/role/.travis.yml +0 -19
  50. data/lib/orats/templates/includes/role/README.md +0 -62
  51. data/lib/orats/templates/includes/role/meta/main.yml +0 -123
  52. data/lib/orats/templates/includes/role/tests/inventory +0 -1
  53. data/lib/orats/templates/includes/role/tests/main.yml +0 -7
  54. data/lib/orats/templates/playbook.rb +0 -119
  55. data/lib/orats/templates/role.rb +0 -144
@@ -1,60 +0,0 @@
1
- require 'orats/commands/common'
2
- require 'orats/commands/diff/parse'
3
- require 'orats/commands/diff/compare'
4
- require 'orats/version'
5
-
6
- module Orats
7
- module Commands
8
- module Diff
9
- class Exec < Commands::Common
10
- include Parse
11
- include Compare
12
-
13
- attr_accessor :diff_list
14
-
15
- def initialize(target_path = '', options = {})
16
- super
17
-
18
- @remote_galaxyfile = galaxyfile url_to_string(@remote_paths[:galaxyfile])
19
- @remote_playbook = playbook url_to_string(@remote_paths[:playbook])
20
- @remote_hosts = hosts url_to_string(@remote_paths[:hosts])
21
- @remote_inventory = inventory url_to_string(@remote_paths[:inventory])
22
-
23
- galaxyfile_path = @options[:galaxyfile]
24
- playbook_path = @options[:playbook]
25
- hosts_path = @options[:hosts]
26
- inventory_path = @options[:inventory]
27
-
28
- if !@options[:inventory].empty? && File.directory?(@options[:inventory])
29
- hosts_path = File.join(inventory_path, 'hosts')
30
- inventory_path = File.join(inventory_path, 'group_vars/all.yml')
31
- end
32
-
33
- if !@options[:playbook].empty? && File.directory?(@options[:playbook])
34
- galaxyfile_path = File.join(playbook_path, 'Galaxyfile')
35
- playbook_path = File.join(playbook_path, 'site.yml')
36
- end
37
-
38
- @your_galaxyfile = galaxyfile file_to_string (galaxyfile_path) unless galaxyfile_path.empty?
39
- @your_playbook = playbook file_to_string(playbook_path) unless playbook_path.empty?
40
- @your_hosts = hosts file_to_string(hosts_path) unless hosts_path.empty?
41
- @your_inventory = inventory file_to_string(inventory_path) unless inventory_path.empty?
42
-
43
- end
44
-
45
- def init
46
- remote_gem_vs_yours
47
-
48
- remote_vs_yours('galaxyfile', @remote_galaxyfile,
49
- @your_galaxyfile, false) unless @your_galaxyfile.nil?
50
- remote_vs_yours('playbook', @remote_playbook,
51
- @your_playbook, true) unless @your_playbook.nil?
52
- remote_vs_yours('hosts', @remote_hosts,
53
- @your_hosts, true) unless @your_hosts.nil?
54
- remote_vs_yours('inventory', @remote_inventory,
55
- @your_inventory, true) unless @your_inventory.nil?
56
- end
57
- end
58
- end
59
- end
60
- end
@@ -1,66 +0,0 @@
1
- module Orats
2
- module Commands
3
- module Diff
4
- module Parse
5
- def gem_version
6
- "v#{url_to_string(@remote_paths[:version]).match(/'(.*)'/)[1..-1].first}"
7
- end
8
-
9
- def galaxyfile(contents)
10
- contents.split
11
- end
12
-
13
- def hosts(contents)
14
- contents.scan(/^\[.*\]/)
15
- end
16
-
17
- def inventory(contents)
18
- # pluck out all of the values contained with {{ }}
19
- ansible_variables = contents.scan(/\{\{([^{{}}]*)\}\}/)
20
-
21
- # remove the leading space
22
- ansible_variables.map! { |line| line.first[0] = '' }
23
-
24
- # match every line that is not a comment and contains a colon
25
- inventory_variables = contents.scan(/^[^#].*:/)
26
-
27
- inventory_variables.map! do |line|
28
- # only strip lines that need it
29
- line.strip! if line.include?(' ') || line.include?("\n")
30
-
31
- # get rid of the trailing colon
32
- line.chomp(':')
33
-
34
- # if a value of a certain variable has a colon then the regex
35
- # picks this up as a match. only take the variable name
36
- # if this happens to occur
37
- line.split(':').first if line.include?(':')
38
- end
39
-
40
- (ansible_variables + inventory_variables).uniq.delete_if(&:empty?)
41
- end
42
-
43
- def playbook(contents)
44
- roles = contents.scan(/^.*role:.*/)
45
-
46
- roles.map! do |line|
47
- line.strip! if line.include?(' ') || line.include?("\n")
48
-
49
- role_parts = line.split('role:')
50
-
51
- # start at the actual role name
52
- line = role_parts[1]
53
-
54
- if line.include?(',')
55
- line = line.split(',').first
56
- end
57
-
58
- line.strip! if line.include?(' ')
59
- end
60
-
61
- roles.uniq
62
- end
63
- end
64
- end
65
- end
66
- end
@@ -1,100 +0,0 @@
1
- require 'securerandom'
2
- require 'orats/commands/common'
3
- require 'orats/commands/project/rails'
4
-
5
- module Orats
6
- module Commands
7
- class Inventory < Common
8
- include Project::Rails
9
-
10
- def initialize(target_path = '', options = {})
11
- super
12
- end
13
-
14
- def init
15
- exit_if_path_exists('inventory')
16
-
17
- create_inventory
18
-
19
- secrets_path = "#{@target_path}/secrets"
20
- create_secrets secrets_path
21
-
22
- log_task 'Update secrets path in group_vars/all.yml'
23
- gsub_file "#{@target_path}/#{fix_path_for_user(Common::RELATIVE_PATHS[:inventory])}",
24
- '/home/yourname/dev/testproj/secrets', File.expand_path(secrets_path)
25
-
26
- log_task 'Update place holder app name in group_vars/all.yml'
27
- gsub_file "#{@target_path}/#{fix_path_for_user(Common::RELATIVE_PATHS[:inventory])}",
28
- 'testproj', File.basename(@target_path)
29
-
30
- log_task 'Add ssh keypair'
31
- run "ssh-keygen -t rsa -P '' -f #{secrets_path}/id_rsa"
32
-
33
- log_task 'Add self signed ssl certificates'
34
- run create_rsa_certificate(secrets_path, 'sslkey.key', 'sslcert.crt')
35
-
36
- log_task 'Add monit pem file'
37
- run "#{create_rsa_certificate(secrets_path,
38
- 'monit.pem', 'monit.pem')} && openssl gendh 512 >> #{secrets_path}/monit.pem"
39
-
40
- log_success
41
- end
42
-
43
- private
44
-
45
- def create_inventory
46
- log_task 'Add ansible inventory'
47
- run "mkdir -p #{@target_path}/inventory/group_vars"
48
-
49
- local_to_user Common::RELATIVE_PATHS[:hosts]
50
- local_to_user Common::RELATIVE_PATHS[:inventory]
51
- end
52
-
53
- def local_to_user(file)
54
- fixed_file = fix_path_for_user(file)
55
-
56
- log_task "Add #{fixed_file}"
57
- run "cp #{base_path}/#{file} #{@target_path}/#{fixed_file}"
58
- end
59
-
60
- def create_secrets(secrets_path)
61
- log_task 'Add ansible secrets'
62
- run "mkdir #{secrets_path}"
63
-
64
- save_secret_string "#{secrets_path}/postgres_password"
65
- save_secret_string "#{secrets_path}/redis_password"
66
- save_secret_string "#{secrets_path}/mail_password"
67
- save_secret_string "#{secrets_path}/rails_token"
68
- save_secret_string "#{secrets_path}/devise_token"
69
- save_secret_string "#{secrets_path}/devise_pepper_token"
70
- end
71
-
72
- def save_secret_string(file)
73
- File.open(file, 'w+') { |f| f.write(SecureRandom.hex(64)) }
74
- end
75
-
76
- def create_rsa_certificate(secrets_path, keyout, out)
77
- "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}"
78
- end
79
-
80
- def log_success
81
- log_status_top 'success', 'Everything has been setup successfully',
82
- :cyan
83
- puts
84
- log_status_bottom 'question', 'Not sure what to do?', :yellow, true
85
- log_status_bottom 'answer', 'Check out the inventory/hosts file',
86
- :white, true
87
- log_status_bottom 'answer', 'Check out the inventory/group_vars/all.yml file', :white
88
-
89
- log_status_bottom 'question', 'Are you new to ansible?', :yellow, true
90
- log_status_bottom 'answer',
91
- 'http://docs.ansible.com/intro_getting_started.html',
92
- :white
93
- end
94
-
95
- def fix_path_for_user(file)
96
- file.sub('templates/includes', '')
97
- end
98
- end
99
- end
100
- end
@@ -1,60 +0,0 @@
1
- require 'orats/commands/common'
2
- require 'orats/commands/project/rails'
3
-
4
- module Orats
5
- module Commands
6
- class Playbook < Common
7
- include Project::Rails
8
-
9
- def initialize(target_path = '', options = {})
10
- super
11
- end
12
-
13
- def init
14
- exit_if_updating_playbook
15
-
16
- rails_template 'playbook'
17
- custom_rails_template unless @options[:custom].empty?
18
-
19
- galaxy_install
20
- log_success
21
- end
22
-
23
- private
24
-
25
- def exit_if_updating_playbook
26
- galaxyfile = File.join(@target_path, 'Galaxyfile')
27
-
28
- if File.exist?(galaxyfile)
29
- galaxy_install 'Update'
30
- exit 1
31
- end
32
- end
33
-
34
- def galaxy_install(git_commit_type='Add')
35
- log_task "#{git_commit_type} ansible roles from the galaxy"
36
-
37
- galaxy_install = "ansible-galaxy install -r #{@target_path}/Galaxyfile --roles-path #{@target_path}/roles --force"
38
-
39
- run galaxy_install
40
-
41
- git_commit "#{git_commit_type} galaxy installed roles"
42
- end
43
-
44
- def log_success
45
- log_status_top 'success', 'Everything has been setup successfully',
46
- :cyan
47
- puts
48
- log_status_bottom 'question', 'Are most of your apps similar?', :yellow, true
49
- log_status_bottom 'answer', 'You only need to generate one playbook and you just did',
50
- :white, true
51
- log_status_bottom 'answer', 'Use the inventory in each project to customize certain things', :white
52
-
53
- log_status_bottom 'question', 'Are you new to ansible?', :yellow, true
54
- log_status_bottom 'answer',
55
- 'http://docs.ansible.com/intro_getting_started.html',
56
- :white
57
- end
58
- end
59
- end
60
- end
@@ -1,74 +0,0 @@
1
- require 'orats/commands/common'
2
- require 'orats/commands/inventory'
3
- require 'orats/commands/project/rails'
4
- require 'orats/commands/project/server'
5
-
6
- module Orats
7
- module Commands
8
- module Project
9
- class Exec < Commands::Common
10
- include Rails
11
- include Server
12
-
13
- PROJECT_TEMPLATES = {
14
- auth: 'add authentication and authorization'
15
- }
16
-
17
- def initialize(target_path = '', options = {})
18
- super
19
-
20
- @active_path = services_path
21
- end
22
-
23
- def list_templates
24
- log_status_top 'templates', 'Available templates to choose from:',
25
- :blue
26
- puts
27
- PROJECT_TEMPLATES.each_pair do |key, value|
28
- log_status_bottom key, value, :cyan, true
29
- log_status_bottom 'usage', "orats project /tmp/foo --template #{key}",
30
- :white
31
- end
32
- end
33
-
34
- def init
35
- check_exit_conditions
36
-
37
- rails_template 'base' do
38
- gsub_postgres_info
39
- gsub_redis_info unless @options[:redis_password].empty?
40
- gsub_project_path
41
- gsub_readme
42
-
43
- bundle_install
44
- bundle_binstubs
45
- spring_binstub
46
-
47
- create_and_migrate_database
48
- generate_home_page
49
- generate_favicons
50
- end
51
-
52
- if template_exist?(@options[:template])
53
- rails_template @options[:template], '--skip ' do
54
- migrate_and_seed_database
55
- end
56
- end
57
-
58
- Commands::Inventory.new(@target_path,
59
- @options).init unless @options[:skip_ansible]
60
-
61
- custom_rails_template unless @options[:custom].empty?
62
-
63
- server_start
64
- end
65
-
66
- private
67
-
68
- def services_path
69
- "#{@target_path}/services/#{File.basename @target_path}"
70
- end
71
- end
72
- end
73
- end
74
- end
@@ -1,162 +0,0 @@
1
- module Orats
2
- module Commands
3
- module Project
4
- module Rails
5
- def check_exit_conditions(check_running_processes: true)
6
- exit_if_process :not_found, 'rails', 'git'
7
- exit_if_process :not_running, 'postgres', 'redis' if check_running_processes
8
- exit_if_path_exists
9
- exit_if_invalid_template
10
- end
11
-
12
- def rails_template(command, flags = '')
13
- orats_template = "--template #{base_path}/templates/#{command}.rb"
14
-
15
- run "rails new #{@active_path} #{flags} --skip-bundle #{orats_template unless command.empty?}"
16
- yield if block_given?
17
- end
18
-
19
- def custom_rails_template
20
- log_task 'Run custom rails template'
21
-
22
- @options[:custom].include?('://') ? url_to_string(@options[:custom])
23
- : file_to_string(@options[:custom])
24
-
25
- rails_template '', "--skip --template #{@options[:custom]}"
26
- end
27
-
28
- def gsub_postgres_info
29
- log_task 'Update the postgres connection details'
30
- gsub_file "#{@active_path}/.env", 'DATABASE_HOST: localhost', "DATABASE_HOST: #{@options[:pg_location]}"
31
- gsub_file "#{@active_path}/.env", ': postgres', ": #{@options[:pg_username]}"
32
- gsub_file "#{@active_path}/.env", ': supersecrets', ": #{@options[:pg_password]}"
33
-
34
- git_commit 'Update the postgres connection details'
35
- end
36
-
37
- def gsub_redis_info
38
- log_task 'Update the redis connection details'
39
- gsub_file "#{@active_path}/.env", 'HE_PASSWORD: ""', "HE_PASSWORD: #{@options[:redis_password]}"
40
- gsub_file "#{@active_path}/.env", 'CACHE_HOST: localhost', "CACHE_HOST: #{@options[:redis_location]}"
41
-
42
- git_commit 'Update the redis connection details'
43
- end
44
-
45
- def gsub_project_path
46
- log_task 'Update the project path'
47
- gsub_file "#{@active_path}/.env", ': /home/yourname/dev/testproj',
48
- ": #{File.expand_path(@active_path)}"
49
-
50
- git_commit 'Update the project path'
51
- end
52
-
53
- def gsub_readme
54
- log_task 'Update the readme'
55
- gsub_file "#{@active_path}/README.md", 'VERSION', VERSION
56
-
57
- git_commit 'Update the version'
58
- end
59
-
60
- def bundle_install
61
- log_task 'Run bundle install, this may take a while'
62
- run_from @active_path, 'bundle install'
63
-
64
- git_commit 'Add Gemfile.lock'
65
- end
66
-
67
- def bundle_binstubs
68
- log_task 'Run bundle binstubs for a few gems'
69
- run_from @active_path, 'bundle binstubs whenever puma sidekiq backup'
70
-
71
- git_commit 'Add binstubs for the important gems'
72
- end
73
-
74
- def spring_binstub
75
- log_task 'Run spring binstub'
76
- run_from @active_path, 'bundle exec spring binstub --all'
77
-
78
- git_commit 'Add spring binstubs for all of the bins'
79
- end
80
-
81
- def run_rake(command)
82
- log_task 'Run rake command'
83
-
84
- run_from @active_path, "bundle exec rake #{command}"
85
- end
86
-
87
- def generate_home_page
88
- kill_spring_servers
89
-
90
- log_task 'Add pages controller with static page'
91
- run_from @active_path, 'bundle exec rails g controller Pages home'
92
-
93
- gsub_file "#{@active_path}/config/routes.rb", " # root 'welcome#index'" do
94
- <<-S
95
- root 'pages#home'
96
- S
97
- end
98
- gsub_file "#{@active_path}/config/routes.rb", " get 'pages/home'\n\n", ''
99
-
100
- gsub_file "#{@active_path}/test/controllers/pages_controller_test.rb",
101
- '"should get home"', "'expect home page'"
102
-
103
- run_from @active_path, "rm app/views/pages/home.html.erb"
104
- Commands::Common.copy_from_local_gem 'new/rails/app/views/pages/home.html.erb',
105
- "#{@active_path}/app/views/pages/home.html.erb"
106
- gsub_file "#{@active_path}/app/views/pages/home.html.erb",
107
- 'vVERSION', VERSION
108
-
109
- git_commit 'Add pages controller with home page'
110
- end
111
-
112
- def generate_favicons
113
- log_task 'Add favicons'
114
- run_rake 'orats:favicons'
115
- git_commit 'Add favicons'
116
- end
117
-
118
- def create_and_migrate_database
119
- run_rake 'db:create:all db:migrate'
120
- git_commit 'Add the database schema file'
121
- end
122
-
123
- def migrate_and_seed_database
124
- run_rake 'db:migrate db:seed'
125
- git_commit 'Update the database schema file'
126
- end
127
-
128
- def template_exist?(template)
129
- Exec::PROJECT_TEMPLATES.include?(template.to_sym)
130
- end
131
-
132
- private
133
-
134
- def exit_if_invalid_template
135
- template = @options[:template] || ''
136
-
137
- if template.length > 0
138
- log_task 'Check if template exists'
139
-
140
- unless template_exist?(template)
141
- log_error 'error', 'Cannot find template', 'message',
142
- "'#{template}' is not a valid template name",
143
- true do
144
- log_status_bottom 'tip',
145
- 'run `orats templates` to get a list of valid templates',
146
- :white
147
- end
148
-
149
- exit 1
150
- end
151
- end
152
- end
153
-
154
- def kill_spring_servers
155
- # rails generators will lock up if a spring server is running,
156
- # so kill them before continuing
157
- system 'pkill -f spring'
158
- end
159
- end
160
- end
161
- end
162
- end
@@ -1,57 +0,0 @@
1
- require 'socket'
2
- require 'timeout'
3
-
4
- module Orats
5
- module Commands
6
- module Project
7
- module Server
8
- START_COMMAND = 'bundle exec foreman start'
9
-
10
- def server_start
11
- @options[:skip_server_start] ? message = 'Start your' : message = 'Starting'
12
-
13
- puts '', '='*80
14
- log_status_top 'action', "#{message} server with the following commands", :cyan
15
- log_status_bottom 'command', "cd #{@active_path}", :magenta, true
16
- log_status_bottom 'command', START_COMMAND, :magenta
17
- puts '='*80, ''
18
-
19
- attempt_to_start unless @options[:skip_server_start]
20
- end
21
-
22
- private
23
-
24
- def attempt_to_start
25
- while port_taken? do
26
- log_status_top 'error', "Another application is using port 3000\n", :red
27
-
28
- exit 1 if no?('Would you like to try running the server again? (y/N)', :cyan)
29
- end
30
-
31
- puts
32
-
33
- run_from @active_path, START_COMMAND
34
- end
35
-
36
- def port_taken?
37
- begin
38
- Timeout::timeout(5) do
39
- begin
40
- s = TCPSocket.new('localhost', 3000)
41
- s.close
42
-
43
- return true
44
- rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
45
- return false
46
- end
47
- end
48
- rescue Timeout::Error
49
- false
50
- end
51
-
52
- false
53
- end
54
- end
55
- end
56
- end
57
- end
@@ -1,70 +0,0 @@
1
- require 'orats/commands/common'
2
- require 'orats/commands/project/rails'
3
-
4
- module Orats
5
- module Commands
6
- class Role < Common
7
- include Project::Rails
8
-
9
- def initialize(target_path = '', options = {})
10
- super
11
- end
12
-
13
- def init
14
- check_exit_conditions check_running_processes: false
15
- exit_if_invalid_role_name
16
-
17
- rails_template 'role'
18
- custom_rails_template unless @options[:custom].empty?
19
-
20
- repo_name = @options[:repo_name].empty? ? base_file_name :
21
- @options[:repo_name]
22
-
23
- log_task 'Update place holder repo name'
24
- gsub_file "#{@target_path}/README.md", 'repo_name',
25
- repo_name
26
- gsub_file "#{@target_path}/tests/main.yml", 'repo_name',
27
- repo_name
28
- git_commit 'Update place holder repo name'
29
-
30
- log_success
31
- end
32
-
33
- private
34
-
35
- def base_file_name
36
- File.basename(@target_path)
37
- end
38
-
39
- def exit_if_invalid_role_name
40
- log_task 'Check if role name is valid'
41
-
42
- unless base_file_name.count('.') == 1
43
- log_error 'error', 'Invalid role name', 'message',
44
- "'#{base_file_name}' is invalid, it must contain 1 period",
45
- true do
46
- log_status_bottom 'tip',
47
- 'Your role name should be github_user.role_name',
48
- :white
49
- end
50
-
51
- exit 1
52
- end
53
- end
54
-
55
- def log_success
56
- log_status_top 'success', 'Everything has been setup successfully',
57
- :cyan
58
- puts
59
- log_status_bottom 'question', 'What should you do next?', :yellow, true
60
- log_status_bottom 'answer', 'Check the readme in the role',
61
- :white
62
-
63
- log_status_bottom 'question', 'Are you new to ansible?', :yellow, true
64
- log_status_bottom 'answer',
65
- 'http://docs.ansible.com/intro_getting_started.html',
66
- :white
67
- end
68
- end
69
- end
70
- end