dust-deploy 0.12.2 → 0.13.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.
- data/bin/dust +192 -66
- data/changelog.md +37 -0
- data/lib/dust.rb +1 -0
- data/lib/dust/messaging.rb +140 -0
- data/lib/dust/recipe.rb +4 -1
- data/lib/dust/recipes/aliases.rb +5 -5
- data/lib/dust/recipes/apt.rb +4 -4
- data/lib/dust/recipes/cjdroute.rb +32 -35
- data/lib/dust/recipes/cups_client.rb +5 -5
- data/lib/dust/recipes/debsecan.rb +4 -4
- data/lib/dust/recipes/duplicity.rb +13 -15
- data/lib/dust/recipes/etc_hosts.rb +3 -3
- data/lib/dust/recipes/hash_check.rb +5 -6
- data/lib/dust/recipes/iptables.rb +12 -17
- data/lib/dust/recipes/limits.rb +19 -11
- data/lib/dust/recipes/locale.rb +14 -14
- data/lib/dust/recipes/logrotate.rb +3 -3
- data/lib/dust/recipes/make.rb +29 -0
- data/lib/dust/recipes/motd.rb +3 -3
- data/lib/dust/recipes/mysql.rb +5 -5
- data/lib/dust/recipes/newrelic.rb +6 -6
- data/lib/dust/recipes/nginx.rb +10 -10
- data/lib/dust/recipes/ntpd.rb +2 -6
- data/lib/dust/recipes/pacemaker.rb +3 -3
- data/lib/dust/recipes/postgres.rb +22 -22
- data/lib/dust/recipes/rc_local.rb +8 -8
- data/lib/dust/recipes/redis.rb +3 -25
- data/lib/dust/recipes/repositories.rb +28 -30
- data/lib/dust/recipes/resolv_conf.rb +16 -16
- data/lib/dust/recipes/ruby_rvm.rb +17 -18
- data/lib/dust/recipes/skel.rb +1 -2
- data/lib/dust/recipes/ssh_authorized_keys.rb +4 -5
- data/lib/dust/recipes/sshd.rb +1 -1
- data/lib/dust/recipes/sudoers.rb +2 -2
- data/lib/dust/recipes/sysctl.rb +12 -8
- data/lib/dust/recipes/zabbix_agent.rb +28 -28
- data/lib/dust/server.rb +114 -115
- data/lib/dust/version.rb +1 -1
- metadata +4 -2
data/bin/dust
CHANGED
@@ -8,6 +8,7 @@ require 'erb'
|
|
8
8
|
require 'fileutils'
|
9
9
|
require 'ipaddress'
|
10
10
|
require 'dust'
|
11
|
+
require 'colorize'
|
11
12
|
|
12
13
|
module Dust
|
13
14
|
class Deploy < Thor::Runner
|
@@ -15,72 +16,167 @@ module Dust
|
|
15
16
|
default_task :list
|
16
17
|
check_unknown_options!
|
17
18
|
|
18
|
-
desc 'deploy
|
19
|
-
'deploy all recipes to the node(s) specified in server.yaml or to all nodes defined in ./nodes/'
|
19
|
+
desc 'deploy', 'deploy all recipes to the node(s) specified in server.yaml or to all nodes defined in ./nodes/'
|
20
20
|
|
21
|
-
|
22
|
-
|
21
|
+
method_option 'yaml', :type => :string, :desc => 'use only this server.yaml'
|
22
|
+
method_option 'filter', :type => :hash, :desc => 'only deploy to these hosts (e.g. environment:staging)'
|
23
|
+
method_option 'recipes', :type => :array, :desc => 'only deploy these recipes'
|
24
|
+
method_option 'proxy', :type => :string, :desc => 'socks proxy to use'
|
25
|
+
method_option 'restart', :type => :boolean, :desc => 'restart services after deploy'
|
26
|
+
method_option 'reload', :type => :boolean, :desc => 'reload services after deploy'
|
27
|
+
method_option 'parallel', :type => :boolean, :desc => 'deploy to all hosts at the same time using threads'
|
28
|
+
method_option 'summary', :type => :string, :desc => 'print summary of all events (all, warning, failed)'
|
23
29
|
|
24
30
|
def deploy
|
25
31
|
return unless check_dust_dir
|
26
32
|
initialize_thorfiles
|
27
33
|
Dust.print_failed 'no servers match this filter' if load_servers.empty?
|
28
34
|
|
29
|
-
|
35
|
+
# set global variables
|
36
|
+
$summary = options['summary']
|
37
|
+
$parallel = options['parallel']
|
38
|
+
$summary = 'all' if $parallel and not $summary
|
39
|
+
|
40
|
+
threads = []
|
41
|
+
@nodes.each_with_index do |node, i|
|
42
|
+
if $parallel
|
43
|
+
threads[i] = Thread.new do
|
44
|
+
run_recipes node, 'deploy'
|
45
|
+
Thread.current['hostname'] = node['hostname']
|
46
|
+
end
|
47
|
+
else
|
48
|
+
run_recipes node, 'deploy'
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
if $parallel
|
53
|
+
print 'waiting for servers: '
|
54
|
+
threads.each { |t| t.join; print t['hostname'].blue + ' ' }
|
55
|
+
puts
|
56
|
+
end
|
57
|
+
|
58
|
+
display_summary($summary) if $summary
|
30
59
|
end
|
31
60
|
|
32
61
|
|
33
|
-
desc 'status
|
34
|
-
'display status of recipes specified by filter'
|
62
|
+
desc 'status', 'display status of recipes specified by filter'
|
35
63
|
|
36
|
-
|
64
|
+
method_option 'yaml', :type => :string, :desc => 'use only this server.yaml'
|
65
|
+
method_option 'filter', :type => :hash, :desc => 'only deploy to these hosts (e.g. environment:staging)'
|
66
|
+
method_option 'recipes', :type => :array, :desc => 'only deploy these recipes'
|
67
|
+
method_option 'proxy', :type => :string, :desc => 'socks proxy to use'
|
68
|
+
method_option 'parallel', :type => :boolean, :desc => 'deploy to all hosts at the same time using threads'
|
69
|
+
method_option 'summary', :type => :string, :desc => 'print summary of all events (all, warning, failed)'
|
37
70
|
|
38
71
|
def status
|
39
72
|
return unless check_dust_dir
|
40
73
|
initialize_thorfiles
|
41
74
|
Dust.print_failed 'no servers match this filter' if load_servers.empty?
|
42
75
|
|
43
|
-
|
76
|
+
# set global variables
|
77
|
+
$summary = options['summary']
|
78
|
+
$parallel = options['parallel']
|
79
|
+
$summary = 'all' if $parallel and not $summary
|
80
|
+
|
81
|
+
threads = []
|
82
|
+
@nodes.each_with_index do |node, i|
|
83
|
+
if $parallel
|
84
|
+
threads[i] = Thread.new do
|
85
|
+
run_recipes node, 'status'
|
86
|
+
Thread.current['hostname'] = node['hostname']
|
87
|
+
end
|
88
|
+
else
|
89
|
+
run_recipes node, 'status'
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
if $parallel
|
94
|
+
print 'waiting for servers: '
|
95
|
+
threads.each { |t| t.join; print t['hostname'].blue + ' ' }
|
96
|
+
puts
|
97
|
+
end
|
98
|
+
|
99
|
+
display_summary($summary) if $summary
|
44
100
|
end
|
45
101
|
|
46
102
|
|
47
|
-
desc 'system_update
|
48
|
-
'perform a full system upgrade (using aptitude, emerge, yum)'
|
103
|
+
desc 'system_update', 'perform a full system upgrade (using aptitude, emerge, yum)'
|
49
104
|
|
50
|
-
|
105
|
+
method_option 'yaml', :type => :string, :desc => 'use only this server.yaml'
|
106
|
+
method_option 'filter', :type => :hash, :desc => 'only deploy to these hosts (e.g. environment:staging)'
|
107
|
+
method_option 'proxy', :type => :string, :desc => 'socks proxy to use'
|
108
|
+
method_option 'parallel', :type => :boolean, :desc => 'deploy to all hosts at the same time using threads'
|
109
|
+
method_option 'summary', :type => :string, :desc => 'print summary of all events (all, warning, failed)'
|
51
110
|
|
52
111
|
def system_update
|
53
112
|
return unless check_dust_dir
|
54
113
|
initialize_thorfiles
|
55
114
|
Dust.print_failed 'no servers match this filter' if load_servers.empty?
|
56
115
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
116
|
+
# set global variables
|
117
|
+
$summary = options['summary']
|
118
|
+
$parallel = options['parallel']
|
119
|
+
$summary = 'all' if $parallel and not $summary
|
120
|
+
|
121
|
+
threads = []
|
122
|
+
@nodes.each_with_index do |node, i|
|
123
|
+
if $parallel
|
124
|
+
threads[i] = Thread.new do
|
125
|
+
run_system_update(node)
|
126
|
+
Thread.current['hostname'] = node['hostname']
|
127
|
+
end
|
128
|
+
else
|
129
|
+
run_system_update(node)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
if $parallel
|
134
|
+
print 'waiting for servers: '
|
135
|
+
threads.each { |t| t.join; print t['hostname'].blue + ' ' }
|
136
|
+
puts
|
63
137
|
end
|
138
|
+
|
139
|
+
display_summary($summary) if $summary
|
64
140
|
end
|
65
141
|
|
66
142
|
|
67
|
-
desc 'exec <command>
|
68
|
-
'run a command on the server'
|
143
|
+
desc 'exec <command>', 'run a command on the server'
|
69
144
|
|
70
|
-
|
145
|
+
method_option 'yaml', :type => :string, :desc => 'use only this server.yaml'
|
146
|
+
method_option 'filter', :type => :hash, :desc => 'only deploy to these hosts (e.g. environment:staging)'
|
147
|
+
method_option 'proxy', :type => :string, :desc => 'socks proxy to use'
|
148
|
+
method_option 'parallel', :type => :boolean, :desc => 'deploy to all hosts at the same time using threads'
|
149
|
+
method_option 'summary', :type => :string, :desc => 'print summary of all events (all, warning, failed)'
|
71
150
|
|
72
151
|
def exec cmd, yaml=''
|
73
152
|
return unless check_dust_dir
|
74
153
|
initialize_thorfiles
|
75
154
|
Dust.print_failed 'no servers match this filter' if load_servers.empty?
|
76
155
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
156
|
+
# set global variables
|
157
|
+
$summary = options['summary']
|
158
|
+
$parallel = options['parallel']
|
159
|
+
$summary = 'all' if $parallel and not $summary
|
160
|
+
|
161
|
+
threads = []
|
162
|
+
@nodes.each_with_index do |node, i|
|
163
|
+
if $parallel
|
164
|
+
threads[i] = Thread.new do
|
165
|
+
run_exec(node, cmd)
|
166
|
+
Thread.current['hostname'] = node['hostname']
|
167
|
+
end
|
168
|
+
else
|
169
|
+
run_exec(node, cmd)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
if $parallel
|
174
|
+
print 'waiting for servers: '
|
175
|
+
threads.each { |t| t.join; print t['hostname'].blue + ' ' }
|
176
|
+
puts
|
83
177
|
end
|
178
|
+
|
179
|
+
display_summary($summary) if $summary
|
84
180
|
end
|
85
181
|
|
86
182
|
|
@@ -111,53 +207,83 @@ module Dust
|
|
111
207
|
end
|
112
208
|
|
113
209
|
# run specified recipes in the given context
|
114
|
-
def run_recipes context
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
next if recipes.empty?
|
123
|
-
|
124
|
-
# connect to server
|
125
|
-
server = Server.new node
|
126
|
-
next unless server.connect
|
127
|
-
|
128
|
-
# runs the method with the recipe name, defined and included in recipe/*.rb
|
129
|
-
# call recipes for each recipe that is defined for this node
|
130
|
-
recipes.each do |recipe, config|
|
131
|
-
::Dust.print_recipe recipe
|
132
|
-
send recipe, 'prepare', server, recipe, context, config, options
|
133
|
-
puts
|
134
|
-
end
|
210
|
+
def run_recipes(node, context)
|
211
|
+
# skip this node if there are no recipes found
|
212
|
+
return unless node['recipes']
|
213
|
+
|
214
|
+
recipes = generate_recipes node, context
|
215
|
+
|
216
|
+
# skip this node unless we're actually having recipes to cook
|
217
|
+
return if recipes.empty?
|
135
218
|
|
136
|
-
|
219
|
+
# connect to server
|
220
|
+
node['server'] = Server.new node
|
221
|
+
return unless node['server'].connect
|
222
|
+
|
223
|
+
# runs the method with the recipe name, defined and included in recipe/*.rb
|
224
|
+
# call recipes for each recipe that is defined for this node
|
225
|
+
recipes.each do |recipe, config|
|
226
|
+
send recipe, 'prepare', node['server'], recipe, context, config, options
|
137
227
|
end
|
228
|
+
|
229
|
+
node['server'].disconnect
|
230
|
+
end
|
231
|
+
|
232
|
+
def run_system_update(node)
|
233
|
+
node['server'] = Server.new(node)
|
234
|
+
return unless node['server'].connect
|
235
|
+
node['server'].system_update
|
236
|
+
node['server'].disconnect
|
237
|
+
end
|
238
|
+
|
239
|
+
def run_exec(node, cmd)
|
240
|
+
node['server'] = Server.new(node)
|
241
|
+
return unless node['server'].connect
|
242
|
+
node['server'].exec(cmd, :live => true)
|
243
|
+
node['server'].disconnect
|
138
244
|
end
|
139
|
-
|
245
|
+
|
140
246
|
# generate list of recipes for this node
|
141
247
|
def generate_recipes node, context
|
142
|
-
recipes = {}
|
248
|
+
recipes = {}
|
143
249
|
node['recipes'].each do |recipe, config|
|
144
|
-
|
250
|
+
|
145
251
|
# in case --recipes was set, skip unwanted recipes
|
146
|
-
next unless options[
|
147
|
-
|
252
|
+
next unless options['recipes'].include?(recipe) if options['recipes']
|
253
|
+
|
148
254
|
# skip disabled recipes
|
149
255
|
next if config == 'disabled' or config.is_a? FalseClass
|
150
|
-
|
256
|
+
|
151
257
|
# check if method and thor task actually exist
|
152
258
|
k = Thor::Util.find_by_namespace recipe
|
153
259
|
next unless k
|
154
260
|
next unless k.method_defined? context
|
155
|
-
|
261
|
+
|
156
262
|
recipes[recipe] = config
|
157
263
|
end
|
158
264
|
recipes
|
159
265
|
end
|
160
|
-
|
266
|
+
|
267
|
+
def display_summary(level)
|
268
|
+
puts "\n\n------------------------------ SUMMARY ------------------------------".red unless $parallel
|
269
|
+
|
270
|
+
@nodes.each do |node|
|
271
|
+
messages = node['server'].messages.collect(level)
|
272
|
+
next if messages.empty?
|
273
|
+
|
274
|
+
node['server'].messages.print_hostname_header(node['hostname'])
|
275
|
+
|
276
|
+
# display non-recipe messages first
|
277
|
+
msgs = messages.delete '_node'
|
278
|
+
msgs.each { |m| print m } if msgs
|
279
|
+
|
280
|
+
# display messages from recipes
|
281
|
+
messages.each do |recipe, msgs|
|
282
|
+
node['server'].messages.print_recipe_header(recipe)
|
283
|
+
msgs.each { |m| print m }
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
161
287
|
|
162
288
|
# overwrite thorfiles to look for tasks in the recipes directories
|
163
289
|
def thorfiles(relevant_to=nil, skip_lookup=false)
|
@@ -171,11 +297,11 @@ module Dust
|
|
171
297
|
# if the argument is empty, load all yaml files in the ./nodes/ directory
|
172
298
|
# if the argument is a directory, load yaml files in this directory
|
173
299
|
# if the argument is a file, load the file.
|
174
|
-
if options[
|
175
|
-
if File.directory? options[
|
176
|
-
yaml_files = Dir["#{options[
|
177
|
-
elsif File.exists? options[
|
178
|
-
yaml_files = options[
|
300
|
+
if options['yaml']
|
301
|
+
if File.directory? options['yaml']
|
302
|
+
yaml_files = Dir["#{options['yaml']}/**/*.yaml"]
|
303
|
+
elsif File.exists? options['yaml']
|
304
|
+
yaml_files = options['yaml']
|
179
305
|
end
|
180
306
|
else
|
181
307
|
yaml_files = Dir['./nodes/**/*.yaml']
|
@@ -195,7 +321,7 @@ module Dust
|
|
195
321
|
# if there is not hostname field in the yaml file,
|
196
322
|
# treat this node file as a template, and skip to the next one
|
197
323
|
next unless node['hostname']
|
198
|
-
|
324
|
+
|
199
325
|
# look for the inherits field in the yaml file,
|
200
326
|
# and merge the templates recursively into this node
|
201
327
|
if node['inherits']
|
@@ -225,7 +351,7 @@ module Dust
|
|
225
351
|
end
|
226
352
|
|
227
353
|
# pass command line proxy option
|
228
|
-
n['proxy'] = options[
|
354
|
+
n['proxy'] = options['proxy'] if options['proxy']
|
229
355
|
|
230
356
|
# add this node to the global node array
|
231
357
|
@nodes.push n unless filtered? n
|
@@ -239,10 +365,10 @@ module Dust
|
|
239
365
|
def filtered? node
|
240
366
|
|
241
367
|
# if filter is not specified, instantly return false
|
242
|
-
return false unless options[
|
368
|
+
return false unless options['filter']
|
243
369
|
|
244
370
|
# remove items if other filter arguments don't match
|
245
|
-
options[
|
371
|
+
options['filter'].each do |k, v|
|
246
372
|
next unless v # skip empty filters
|
247
373
|
|
248
374
|
# filter if this node doesn't even have the attribute
|
data/changelog.md
CHANGED
@@ -1,6 +1,43 @@
|
|
1
1
|
Changelog
|
2
2
|
=============
|
3
3
|
|
4
|
+
0.13.0
|
5
|
+
------------
|
6
|
+
|
7
|
+
- introduces --parallel and --summary
|
8
|
+
--summary -> warning e.g. shows a summary with all errors and warnings after completion
|
9
|
+
|
10
|
+
--parallel -> deploy to all hosts in parallel, using threads
|
11
|
+
|
12
|
+
- switches to new messaging system. using ::Dust.pring_* methods is now deprecated
|
13
|
+
please migrate your recipes to the new @node.messages.add() system
|
14
|
+
|
15
|
+
::Dust.print_msg('checking something')
|
16
|
+
::Dust.print_ok
|
17
|
+
|
18
|
+
msg = @node.messages.add('checking something')
|
19
|
+
msg.ok
|
20
|
+
|
21
|
+
|
22
|
+
::Dust.print_ok('this went well')
|
23
|
+
|
24
|
+
@node.message.add('this went well').ok
|
25
|
+
|
26
|
+
|
27
|
+
::Dust.print_message('executing something')
|
28
|
+
::Dust.print_result(ret)
|
29
|
+
|
30
|
+
msg = @node.message.add('executing something')
|
31
|
+
msg.parse_result(ret)
|
32
|
+
|
33
|
+
|
34
|
+
- redis doesn't configure sysctl anymore, please use sysctl redis template
|
35
|
+
|
36
|
+
recipes:
|
37
|
+
sysctl:
|
38
|
+
templates: redis
|
39
|
+
|
40
|
+
|
4
41
|
0.12.2
|
5
42
|
------------
|
6
43
|
|
data/lib/dust.rb
CHANGED
@@ -0,0 +1,140 @@
|
|
1
|
+
require 'colorize'
|
2
|
+
|
3
|
+
module Dust
|
4
|
+
|
5
|
+
class Messages
|
6
|
+
attr_reader :current_recipe
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@store = {}
|
10
|
+
|
11
|
+
# store non-recipe messages in _node
|
12
|
+
start_recipe('_node')
|
13
|
+
end
|
14
|
+
|
15
|
+
def add(msg, options = {})
|
16
|
+
m = Message.new(msg, options)
|
17
|
+
@store[@current_recipe] << m
|
18
|
+
m
|
19
|
+
end
|
20
|
+
|
21
|
+
def start_recipe(recipe)
|
22
|
+
@current_recipe = recipe
|
23
|
+
|
24
|
+
# display recipe header, unless we're starting non-recipe messages
|
25
|
+
print_recipe_header(recipe) if recipe != '_node' and not $parallel
|
26
|
+
|
27
|
+
@store[@current_recipe] = []
|
28
|
+
end
|
29
|
+
|
30
|
+
# print hostname
|
31
|
+
def print_hostname_header(host)
|
32
|
+
puts "\n\n[ #{host} ]".blue
|
33
|
+
end
|
34
|
+
|
35
|
+
# print recipe name
|
36
|
+
def print_recipe_header(recipe)
|
37
|
+
puts "\n|#{recipe}|".green
|
38
|
+
end
|
39
|
+
|
40
|
+
def collect(level = 'all')
|
41
|
+
case level
|
42
|
+
when 'all'
|
43
|
+
l = [ 'none', 'ok', 'warning', 'failed' ]
|
44
|
+
when 'warning'
|
45
|
+
l = [ 'warning', 'failed' ]
|
46
|
+
when 'failed'
|
47
|
+
l = [ 'failed' ]
|
48
|
+
end
|
49
|
+
|
50
|
+
errors = {}
|
51
|
+
@store.each do |recipe, messages|
|
52
|
+
messages.each do |msg|
|
53
|
+
if l.include? msg.status
|
54
|
+
errors[recipe] ||= []
|
55
|
+
errors[recipe] << msg.text
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
errors
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
class Message
|
66
|
+
attr_reader :text, :status
|
67
|
+
|
68
|
+
def initialize(msg = '', options = {})
|
69
|
+
# merge default options
|
70
|
+
@options = { :quiet => false, :indent => 1 }.merge options
|
71
|
+
|
72
|
+
# autoflush
|
73
|
+
$stdout.sync = true
|
74
|
+
|
75
|
+
# just return if quiet mode is on
|
76
|
+
unless @options[:quiet]
|
77
|
+
# default status is 'message'
|
78
|
+
@status = 'none'
|
79
|
+
|
80
|
+
@text = indent + msg
|
81
|
+
print @text unless $parallel
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def ok(msg = '')
|
86
|
+
unless @options[:quiet]
|
87
|
+
@text << msg + ' [ ok ]'.green + "\n"
|
88
|
+
puts msg + ' [ ok ]'.green unless $parallel
|
89
|
+
@status = 'ok'
|
90
|
+
end
|
91
|
+
|
92
|
+
true
|
93
|
+
end
|
94
|
+
|
95
|
+
def warning(msg = '')
|
96
|
+
unless @options[:quiet]
|
97
|
+
@text << msg + ' [ warning ]'.yellow + "\n"
|
98
|
+
puts msg + ' [ warning ]'.yellow unless $parallel
|
99
|
+
@status = 'warning'
|
100
|
+
end
|
101
|
+
|
102
|
+
true
|
103
|
+
end
|
104
|
+
|
105
|
+
def failed(msg = '')
|
106
|
+
unless @options[:quiet]
|
107
|
+
@text << msg + ' [ failed ]'.red + "\n"
|
108
|
+
puts msg + ' [ failed ]'.red unless $parallel
|
109
|
+
@status = 'failed'
|
110
|
+
end
|
111
|
+
|
112
|
+
false
|
113
|
+
end
|
114
|
+
|
115
|
+
def parse_result(ret)
|
116
|
+
return ok if ret == 0 or ret.is_a? TrueClass
|
117
|
+
failed
|
118
|
+
end
|
119
|
+
|
120
|
+
# prints stdout in grey and stderr in red (if existend)
|
121
|
+
def print_output(ret)
|
122
|
+
@text << indent + ret[:stdout].chomp.green + "\n" unless ret[:stdout].empty?
|
123
|
+
@text << indent + ret[:stderr].chomp.red + "\n" unless ret[:stderr].empty?
|
124
|
+
|
125
|
+
print @text unless $parallel
|
126
|
+
end
|
127
|
+
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
# indent according to @options[:indent]
|
132
|
+
# indent 0
|
133
|
+
# - indent 1
|
134
|
+
# - indent 2
|
135
|
+
def indent
|
136
|
+
return '' if @options[:quiet] or @options[:indent] == 0
|
137
|
+
' ' + ' ' * (@options[:indent] - 1) + '- '
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|