dust-deploy 0.12.2 → 0.13.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|