omnibus-ctl 0.3.6 → 0.6.9
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.
- checksums.yaml +5 -5
- data/LICENSE +201 -0
- data/bin/omnibus-ctl +3 -3
- data/lib/omnibus-ctl/version.rb +1 -1
- data/lib/omnibus-ctl.rb +501 -158
- metadata +52 -11
- data/README.md +0 -87
data/lib/omnibus-ctl.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
#
|
2
|
-
# Copyright:: Copyright (c) 2012 Opscode, Inc.
|
1
|
+
# Copyright (c) 2012-2015 Chef Software, Inc.
|
3
2
|
# License:: Apache License, Version 2.0
|
4
3
|
#
|
5
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
@@ -16,127 +15,157 @@
|
|
16
15
|
#
|
17
16
|
|
18
17
|
require "omnibus-ctl/version"
|
19
|
-
require
|
20
|
-
require
|
18
|
+
require "chef-utils/dist" unless defined?(ChefUtils)
|
19
|
+
require "json" unless defined?(JSON)
|
20
|
+
require "fileutils" unless defined?(FileUtils)
|
21
|
+
|
22
|
+
# For license checks
|
23
|
+
require "io/console"
|
24
|
+
require "io/wait"
|
21
25
|
|
22
26
|
module Omnibus
|
23
27
|
class Ctl
|
24
28
|
|
25
|
-
File
|
29
|
+
File.umask(022)
|
26
30
|
|
27
|
-
SV_COMMAND_NAMES = %w
|
31
|
+
SV_COMMAND_NAMES = %w{status up down once pause cont hup alarm int quit
|
28
32
|
term kill start stop restart shutdown force-stop
|
29
|
-
force-reload force-restart force-shutdown check
|
33
|
+
force-reload force-restart force-shutdown check usr1 usr2}.freeze
|
30
34
|
|
31
35
|
attr_accessor :name, :display_name, :log_exclude, :base_path, :sv_path,
|
32
36
|
:service_path, :etc_path, :data_path, :log_path, :command_map, :category_command_map,
|
33
37
|
:fh_output, :kill_users, :verbose, :log_path_exclude
|
34
38
|
|
35
|
-
|
39
|
+
attr_reader :backup_dir, :exe_name
|
40
|
+
|
41
|
+
def initialize(name, merge_service_commands = true, disp_name = nil)
|
36
42
|
@name = name
|
37
|
-
@service_commands =
|
38
|
-
@display_name = name
|
43
|
+
@service_commands = merge_service_commands
|
44
|
+
@display_name = disp_name || name
|
39
45
|
@base_path = "/opt/#{name}"
|
40
46
|
@sv_path = File.join(@base_path, "sv")
|
41
47
|
@service_path = File.join(@base_path, "service")
|
42
48
|
@log_path = "/var/log/#{name}"
|
43
49
|
@data_path = "/var/opt/#{name}"
|
44
50
|
@etc_path = "/etc/#{name}"
|
45
|
-
@log_exclude =
|
46
|
-
@log_path_exclude = [
|
51
|
+
@log_exclude = "(config|lock|@|bz2|gz|gzip|tbz2|tgz|txz|xz|zip)"
|
52
|
+
@log_path_exclude = ["*/sasl/*"]
|
47
53
|
@fh_output = STDOUT
|
48
54
|
@kill_users = []
|
49
55
|
@verbose = false
|
56
|
+
@quiet = false
|
57
|
+
@exe_name = File.basename($0)
|
58
|
+
@force_exit = false
|
59
|
+
@global_pre_hooks = {}
|
60
|
+
|
61
|
+
# TODO(ssd) 2017-03-28: Set SVDIR explicitly. Once we fix a bug
|
62
|
+
# in our debian support, where we rely on system-installed
|
63
|
+
# runit, we can likely change this back to ENV.delete("SVDIR")
|
64
|
+
ENV["SVDIR"] = service_path
|
65
|
+
|
50
66
|
# backwards compat command map that does not have categories
|
51
|
-
@command_map = {
|
67
|
+
@command_map = {}
|
52
68
|
|
53
69
|
# categoired commands that we want by default
|
54
70
|
@category_command_map = {
|
55
71
|
"general" => {
|
56
72
|
"show-config" => {
|
57
|
-
:
|
58
|
-
:
|
73
|
+
desc: "Show the configuration that would be generated by reconfigure.",
|
74
|
+
arity: 1,
|
59
75
|
},
|
60
76
|
"reconfigure" => {
|
61
|
-
:
|
62
|
-
:
|
77
|
+
desc: "Reconfigure the application.",
|
78
|
+
arity: 2,
|
63
79
|
},
|
64
80
|
"cleanse" => {
|
65
|
-
:
|
66
|
-
:
|
81
|
+
desc: "Delete *all* #{display_name} data, and start from scratch.",
|
82
|
+
arity: 2,
|
67
83
|
},
|
68
84
|
"uninstall" => {
|
69
|
-
:
|
70
|
-
:
|
85
|
+
arity: 1,
|
86
|
+
desc: "Kill all processes and uninstall the process supervisor (data will be preserved).",
|
71
87
|
},
|
72
88
|
"help" => {
|
73
|
-
:
|
74
|
-
:
|
75
|
-
}
|
76
|
-
}
|
89
|
+
arity: 1,
|
90
|
+
desc: "Print this help message.",
|
91
|
+
},
|
92
|
+
},
|
77
93
|
}
|
78
94
|
service_command_map = {
|
79
95
|
"service-management" => {
|
80
96
|
"service-list" => {
|
81
|
-
:
|
82
|
-
:
|
97
|
+
arity: 1,
|
98
|
+
desc: "List all the services (enabled services appear with a *.)",
|
83
99
|
},
|
84
100
|
"status" => {
|
85
|
-
:
|
86
|
-
:
|
101
|
+
desc: "Show the status of all the services.",
|
102
|
+
arity: 2,
|
87
103
|
},
|
88
104
|
"tail" => {
|
89
|
-
:
|
90
|
-
:
|
105
|
+
desc: "Watch the service logs of all enabled services.",
|
106
|
+
arity: 2,
|
91
107
|
},
|
92
108
|
"start" => {
|
93
|
-
:
|
94
|
-
:
|
109
|
+
desc: "Start services if they are down, and restart them if they stop.",
|
110
|
+
arity: 2,
|
95
111
|
},
|
96
112
|
"stop" => {
|
97
|
-
:
|
98
|
-
:
|
113
|
+
desc: "Stop the services, and do not restart them.",
|
114
|
+
arity: 2,
|
99
115
|
},
|
100
116
|
"restart" => {
|
101
|
-
:
|
102
|
-
:
|
117
|
+
desc: "Stop the services if they are running, then start them again.",
|
118
|
+
arity: 2,
|
103
119
|
},
|
104
120
|
"once" => {
|
105
|
-
:
|
106
|
-
:
|
121
|
+
desc: "Start the services if they are down. Do not restart them if they stop.",
|
122
|
+
arity: 2,
|
107
123
|
},
|
108
124
|
"hup" => {
|
109
|
-
:
|
110
|
-
:
|
125
|
+
desc: "Send the services a HUP.",
|
126
|
+
arity: 2,
|
111
127
|
},
|
112
128
|
"term" => {
|
113
|
-
:
|
114
|
-
:
|
129
|
+
desc: "Send the services a TERM.",
|
130
|
+
arity: 2,
|
115
131
|
},
|
116
132
|
"int" => {
|
117
|
-
:
|
118
|
-
:
|
133
|
+
desc: "Send the services an INT.",
|
134
|
+
arity: 2,
|
119
135
|
},
|
120
136
|
"kill" => {
|
121
|
-
:
|
122
|
-
:
|
137
|
+
desc: "Send the services a KILL.",
|
138
|
+
arity: 2,
|
123
139
|
},
|
124
140
|
"graceful-kill" => {
|
125
|
-
:
|
126
|
-
:
|
127
|
-
}
|
128
|
-
|
141
|
+
desc: "Attempt a graceful stop, then SIGKILL the entire process group.",
|
142
|
+
arity: 2,
|
143
|
+
},
|
144
|
+
"usr1" => {
|
145
|
+
desc: "Send the services a USR1.",
|
146
|
+
arity: 2,
|
147
|
+
},
|
148
|
+
"usr2" => {
|
149
|
+
desc: "Send the services a USR2.",
|
150
|
+
arity: 2,
|
151
|
+
},
|
152
|
+
},
|
129
153
|
}
|
130
154
|
@category_command_map.merge!(service_command_map) if service_commands?
|
131
155
|
end
|
132
156
|
|
157
|
+
def self.to_method_name(name)
|
158
|
+
name.gsub(/-/, "_").to_sym
|
159
|
+
end
|
160
|
+
|
161
|
+
def to_method_name(name)
|
162
|
+
Ctl.to_method_name(name)
|
163
|
+
end
|
164
|
+
|
133
165
|
SV_COMMAND_NAMES.each do |sv_cmd|
|
134
|
-
|
135
|
-
Omnibus::Ctl.class_eval <<-EOH
|
136
|
-
def #{method_name}(*args)
|
166
|
+
define_method to_method_name(sv_cmd) do |*args|
|
137
167
|
run_sv_command(*args)
|
138
168
|
end
|
139
|
-
EOH
|
140
169
|
end
|
141
170
|
|
142
171
|
# merges category_command_map and command_map,
|
@@ -160,29 +189,30 @@ module Omnibus
|
|
160
189
|
end
|
161
190
|
|
162
191
|
def load_file(filepath)
|
163
|
-
eval(IO.read(filepath))
|
192
|
+
eval(IO.read(filepath), nil, filepath, 1) # rubocop: disable Security/Eval
|
164
193
|
end
|
165
194
|
|
166
|
-
def add_command(name, description, arity=1, &block)
|
167
|
-
@command_map[name] = { :
|
168
|
-
|
169
|
-
# Ruby does not like dashes in method names
|
170
|
-
method_name = name.gsub(/-/, "_")
|
171
|
-
metaclass.send(:define_method, method_name.to_sym) { |*args| block.call(*args) }
|
195
|
+
def add_command(name, description, arity = 1, &block)
|
196
|
+
@command_map[name] = { desc: description, arity: arity }
|
197
|
+
self.class.send(:define_method, to_method_name(name).to_sym) { |*args| block.call(*args) }
|
172
198
|
end
|
173
199
|
|
174
|
-
def add_command_under_category(name, category, description, arity=1, &block)
|
200
|
+
def add_command_under_category(name, category, description, arity = 1, &block)
|
175
201
|
# add new category if it doesn't exist
|
176
|
-
@category_command_map[category]
|
177
|
-
@category_command_map[category][name] = { :
|
178
|
-
|
179
|
-
# Ruby does not like dashes in method names
|
180
|
-
method_name = name.gsub(/-/, "_")
|
181
|
-
metaclass.send(:define_method, method_name.to_sym) { |*args| block.call(*args) }
|
202
|
+
@category_command_map[category] ||= {}
|
203
|
+
@category_command_map[category][name] = { desc: description, arity: arity }
|
204
|
+
self.class.send(:define_method, to_method_name(name).to_sym) { |*args| block.call(*args) }
|
182
205
|
end
|
183
206
|
|
184
|
-
def
|
185
|
-
|
207
|
+
def add_global_pre_hook(name, &block)
|
208
|
+
method_name = to_method_name("#{name}_global_pre_hook").to_sym
|
209
|
+
@global_pre_hooks[name] = method_name
|
210
|
+
self.class.send(:define_method, method_name, block)
|
211
|
+
end
|
212
|
+
|
213
|
+
def exit!(code)
|
214
|
+
@force_exit = true
|
215
|
+
code
|
186
216
|
end
|
187
217
|
|
188
218
|
def log(msg)
|
@@ -190,17 +220,17 @@ module Omnibus
|
|
190
220
|
end
|
191
221
|
|
192
222
|
def get_pgrp_from_pid(pid)
|
193
|
-
ps
|
223
|
+
ps = `which ps`.chomp
|
194
224
|
`#{ps} -p #{pid} -o pgrp=`.chomp
|
195
225
|
end
|
196
226
|
|
197
227
|
def get_pids_from_pgrp(pgrp)
|
198
|
-
pgrep
|
228
|
+
pgrep = `which pgrep`.chomp
|
199
229
|
`#{pgrep} -g #{pgrp}`.split(/\n/).join(" ")
|
200
230
|
end
|
201
231
|
|
202
232
|
def sigkill_pgrp(pgrp)
|
203
|
-
pkill
|
233
|
+
pkill = `which pkill`.chomp
|
204
234
|
run_command("#{pkill} -9 -g #{pgrp}")
|
205
235
|
end
|
206
236
|
|
@@ -218,40 +248,34 @@ module Omnibus
|
|
218
248
|
exit! 0
|
219
249
|
end
|
220
250
|
|
221
|
-
def cleanup_procs_and_nuke(filestr)
|
222
|
-
|
223
|
-
run_sv_command("stop")
|
224
|
-
rescue SystemExit
|
225
|
-
end
|
251
|
+
def cleanup_procs_and_nuke(filestr, calling_method = nil)
|
252
|
+
run_sv_command("stop")
|
226
253
|
|
227
|
-
FileUtils.rm_f("/etc/init/#{name}-runsvdir.conf") if File.
|
228
|
-
run_command("egrep -v '#{base_path}/embedded/bin/runsvdir-start' /etc/inittab > /etc/inittab.new && mv /etc/inittab.new /etc/inittab") if File.
|
254
|
+
FileUtils.rm_f("/etc/init/#{name}-runsvdir.conf") if File.exist?("/etc/init/#{name}-runsvdir.conf")
|
255
|
+
run_command("egrep -v '#{base_path}/embedded/bin/runsvdir-start' /etc/inittab > /etc/inittab.new && mv /etc/inittab.new /etc/inittab") if File.exist?("/etc/inittab")
|
229
256
|
run_command("kill -1 1")
|
230
257
|
|
231
|
-
backup_dir = Time.now.strftime("/root/#{name}-cleanse-%FT%R")
|
232
|
-
|
258
|
+
@backup_dir = Time.now.strftime("/root/#{name}-cleanse-%FT%R")
|
259
|
+
|
260
|
+
FileUtils.mkdir_p("/root") unless File.exist?("/root")
|
233
261
|
FileUtils.rm_rf(backup_dir)
|
234
|
-
FileUtils.cp_r(etc_path, backup_dir) if File.
|
262
|
+
FileUtils.cp_r(etc_path, backup_dir) if File.exist?(etc_path)
|
235
263
|
run_command("rm -rf #{filestr}")
|
264
|
+
graceful_kill
|
236
265
|
|
237
|
-
|
238
|
-
|
239
|
-
rescue SystemExit
|
240
|
-
end
|
241
|
-
|
242
|
-
run_command("pkill -HUP -u #{kill_users.join(',')}") if kill_users.length > 0
|
266
|
+
log "Terminating processes running under application users. This will take a few seconds."
|
267
|
+
run_command("pkill -HUP -u #{kill_users.join(",")}") if kill_users.length > 0
|
243
268
|
run_command("pkill -HUP -f 'runsvdir -P #{service_path}'")
|
244
269
|
sleep 3
|
245
|
-
run_command("pkill -TERM -u #{kill_users.join(
|
270
|
+
run_command("pkill -TERM -u #{kill_users.join(",")}") if kill_users.length > 0
|
246
271
|
run_command("pkill -TERM -f 'runsvdir -P #{service_path}'")
|
247
272
|
sleep 3
|
248
|
-
run_command("pkill -KILL -u #{kill_users.join(
|
273
|
+
run_command("pkill -KILL -u #{kill_users.join(",")}") if kill_users.length > 0
|
249
274
|
run_command("pkill -KILL -f 'runsvdir -P #{service_path}'")
|
250
275
|
|
251
276
|
get_all_services.each do |die_daemon_die|
|
252
277
|
run_command("pkill -KILL -f 'runsv #{die_daemon_die}'")
|
253
278
|
end
|
254
|
-
|
255
279
|
log "Your config files have been backed up to #{backup_dir}."
|
256
280
|
exit! 0
|
257
281
|
end
|
@@ -260,16 +284,56 @@ module Omnibus
|
|
260
284
|
cleanup_procs_and_nuke("/tmp/opt")
|
261
285
|
end
|
262
286
|
|
263
|
-
def
|
264
|
-
|
265
|
-
|
266
|
-
|
287
|
+
def scary_cleanse_warning(*args)
|
288
|
+
just_do_it = args.include?("yes")
|
289
|
+
with_external = ARGV.include?("--with-external")
|
290
|
+
log <<EOM
|
291
|
+
*******************************************************************
|
292
|
+
* * * * * * * * * * * STOP AND READ * * * * * * * * * *
|
293
|
+
*******************************************************************
|
294
|
+
This command will delete *all* local configuration, log, and
|
295
|
+
variable data associated with #{display_name}.
|
296
|
+
EOM
|
297
|
+
if with_external
|
298
|
+
log <<EOM
|
299
|
+
This will also delete externally hosted #{display_name} data.
|
300
|
+
This means that any service you have configured as 'external'
|
301
|
+
will have any #{display_name} permanently deleted.
|
302
|
+
EOM
|
303
|
+
elsif not external_services.empty?
|
304
|
+
log <<EOM
|
305
|
+
|
306
|
+
Important note: If you also wish to delete externally hosted #{display_name}
|
307
|
+
data, please hit CTRL+C now and run '#{exe_name} cleanse --with-external'
|
308
|
+
EOM
|
309
|
+
end
|
310
|
+
|
311
|
+
unless just_do_it
|
312
|
+
data = with_external ? "local, and remote data" : "and local data"
|
313
|
+
log <<EOM
|
314
|
+
|
315
|
+
You have 60 seconds to hit CTRL-C before configuration,
|
316
|
+
logs, #{data} for this application are permanently
|
317
|
+
deleted.
|
318
|
+
*******************************************************************
|
319
|
+
|
320
|
+
EOM
|
321
|
+
begin
|
322
|
+
sleep 60
|
323
|
+
rescue Interrupt
|
324
|
+
log ""
|
325
|
+
exit 0
|
326
|
+
end
|
267
327
|
end
|
268
|
-
|
328
|
+
end
|
329
|
+
|
330
|
+
def cleanse(*args)
|
331
|
+
scary_cleanse_warning(*args)
|
332
|
+
cleanup_procs_and_nuke("#{service_path}/* /tmp/opt #{data_path} #{etc_path} #{log_path}", "cleanse")
|
269
333
|
end
|
270
334
|
|
271
335
|
def get_all_services_files
|
272
|
-
Dir[File.join(sv_path,
|
336
|
+
Dir[File.join(sv_path, "*")]
|
273
337
|
end
|
274
338
|
|
275
339
|
def get_all_services
|
@@ -280,8 +344,10 @@ module Omnibus
|
|
280
344
|
File.symlink?("#{service_path}/#{service_name}")
|
281
345
|
end
|
282
346
|
|
283
|
-
def run_sv_command(sv_cmd, service=nil)
|
347
|
+
def run_sv_command(sv_cmd, service = nil)
|
284
348
|
exit_status = 0
|
349
|
+
sv_cmd = "1" if sv_cmd == "usr1"
|
350
|
+
sv_cmd = "2" if sv_cmd == "usr2"
|
285
351
|
if service
|
286
352
|
exit_status += run_sv_command_for_service(sv_cmd, service)
|
287
353
|
else
|
@@ -296,10 +362,10 @@ module Omnibus
|
|
296
362
|
def run_sv_command_for_service(sv_cmd, service_name)
|
297
363
|
if service_enabled?(service_name)
|
298
364
|
status = run_command("#{base_path}/init/#{service_name} #{sv_cmd}")
|
299
|
-
|
365
|
+
status.exitstatus
|
300
366
|
else
|
301
367
|
log "#{service_name} disabled" if sv_cmd == "status" && verbose
|
302
|
-
|
368
|
+
0
|
303
369
|
end
|
304
370
|
end
|
305
371
|
|
@@ -331,7 +397,7 @@ module Omnibus
|
|
331
397
|
end
|
332
398
|
|
333
399
|
# All other services respond normally to p-c-c * commands
|
334
|
-
|
400
|
+
true
|
335
401
|
end
|
336
402
|
|
337
403
|
# removed services are configured via the attributes file in
|
@@ -341,12 +407,7 @@ module Omnibus
|
|
341
407
|
# not exist), we know that this will be a new server, and we don't
|
342
408
|
# have to worry about pre-upgrade services hanging around. We can safely
|
343
409
|
# return an empty array when running_config is nil
|
344
|
-
|
345
|
-
key = package_name.gsub(/-/, '_')
|
346
|
-
cfg[key]["removed_services"] || []
|
347
|
-
else
|
348
|
-
[]
|
349
|
-
end
|
410
|
+
running_package_config["removed_services"] || []
|
350
411
|
end
|
351
412
|
|
352
413
|
# hidden services are configured via the attributes file in
|
@@ -359,12 +420,7 @@ module Omnibus
|
|
359
420
|
# not exist), we don't want to return nil, just return an empty array.
|
360
421
|
# worse result with doing that is services that we don't want to show up in
|
361
422
|
# c-s-c status will show up.
|
362
|
-
|
363
|
-
key = package_name.gsub(/-/, '_')
|
364
|
-
cfg[key]["hidden_services"] || []
|
365
|
-
else
|
366
|
-
[]
|
367
|
-
end
|
423
|
+
running_package_config["hidden_services"] || []
|
368
424
|
end
|
369
425
|
|
370
426
|
# translate the name from the config to the package name.
|
@@ -382,54 +438,147 @@ module Omnibus
|
|
382
438
|
|
383
439
|
# returns nil when chef-server-running.json does not exist
|
384
440
|
def running_config
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
441
|
+
fname = "#{etc_path}/#{::ChefUtils::Dist::Server::SERVER}-running.json"
|
442
|
+
@running_config ||= if File.exist?(fname)
|
443
|
+
JSON.parse(File.read(fname))
|
444
|
+
end
|
445
|
+
end
|
446
|
+
|
447
|
+
# Helper function that returns the hash of config hashes that have the key 'external' : true
|
448
|
+
# in the running config. If none exist it will return an empty hash.
|
449
|
+
def external_services
|
450
|
+
@external_services ||= running_package_config.select { |k, v| v.class == Hash and v["external"] == true }
|
451
|
+
end
|
452
|
+
|
453
|
+
# Helper function that returns true if an external service entry exists for
|
454
|
+
# the named service
|
455
|
+
def service_external?(service)
|
456
|
+
return false if service.nil?
|
457
|
+
|
458
|
+
external_services.key? service
|
459
|
+
end
|
460
|
+
|
461
|
+
# Gives package config from the running_config.
|
462
|
+
# If there is no running config or if package_name doens't
|
463
|
+
# reference a valid key, this will return an empty hash
|
464
|
+
def running_package_config
|
465
|
+
if (cfg = running_config)
|
466
|
+
cfg[package_name.gsub(/-/, "_")] || {}
|
467
|
+
else
|
468
|
+
{}
|
389
469
|
end
|
390
470
|
end
|
391
471
|
|
472
|
+
# This returns running_config[package][service].
|
473
|
+
#
|
474
|
+
# If there is no running_config or is no matching key
|
475
|
+
# it will return nil.
|
476
|
+
def running_service_config(service)
|
477
|
+
running_package_config[service]
|
478
|
+
end
|
479
|
+
|
392
480
|
def remove_old_node_state
|
393
481
|
node_cache_path = "#{base_path}/embedded/nodes/"
|
394
482
|
status = run_command("rm -rf #{node_cache_path}")
|
395
|
-
|
483
|
+
unless status.success?
|
396
484
|
log "Could not remove cached node state!"
|
397
|
-
exit
|
485
|
+
exit 1
|
398
486
|
end
|
399
487
|
end
|
400
488
|
|
401
|
-
def run_chef(attr_location, args=
|
489
|
+
def run_chef(attr_location, args = "")
|
490
|
+
if @verbose
|
491
|
+
log_level = "-l debug"
|
492
|
+
elsif @quiet
|
493
|
+
# null formatter is awfully quiet, so let them know we're doing something.
|
494
|
+
log "Reconfiguring #{display_name}."
|
495
|
+
log_level = "-l fatal -F null"
|
496
|
+
else
|
497
|
+
log_level = ""
|
498
|
+
end
|
402
499
|
remove_old_node_state
|
403
|
-
cmd = "#{base_path}/embedded/bin/chef-client -z -c #{base_path}/embedded/cookbooks/solo.rb -j #{attr_location}"
|
500
|
+
cmd = "#{base_path}/embedded/bin/chef-client #{log_level} -z -c #{base_path}/embedded/cookbooks/solo.rb -j #{attr_location}"
|
404
501
|
cmd += " #{args}" unless args.empty?
|
405
502
|
run_command(cmd)
|
406
503
|
end
|
407
504
|
|
408
505
|
def show_config(*args)
|
409
|
-
status = run_chef("#{base_path}/embedded/cookbooks/show-config.json", "-l fatal")
|
410
|
-
|
411
|
-
exit! 0
|
412
|
-
else
|
413
|
-
exit! 1
|
414
|
-
end
|
506
|
+
status = run_chef("#{base_path}/embedded/cookbooks/show-config.json", "-l fatal -F null")
|
507
|
+
exit! status.success? ? 0 : 1
|
415
508
|
end
|
416
509
|
|
417
|
-
def reconfigure(
|
510
|
+
def reconfigure(*args)
|
511
|
+
# args being passed to this command does not include the ones that are
|
512
|
+
# starting with "-". See #is_option? method. If it is starting with "-"
|
513
|
+
# then it is treated as a option and we need to look for them in ARGV.
|
514
|
+
check_license_acceptance(ARGV.include?("--accept-license"))
|
515
|
+
|
418
516
|
status = run_chef("#{base_path}/embedded/cookbooks/dna.json")
|
419
517
|
if status.success?
|
420
518
|
log "#{display_name} Reconfigured!"
|
421
|
-
exit! 0
|
519
|
+
exit! 0
|
422
520
|
else
|
423
521
|
exit! 1
|
424
522
|
end
|
425
523
|
end
|
426
524
|
|
525
|
+
def check_license_acceptance(override_accept = false)
|
526
|
+
license_guard_file_path = File.join(data_path, ".license.accepted")
|
527
|
+
|
528
|
+
# If the project does not have a license we do not have
|
529
|
+
# any license to accept.
|
530
|
+
return unless File.exist?(project_license_path)
|
531
|
+
|
532
|
+
unless File.exist?(license_guard_file_path)
|
533
|
+
if override_accept || ask_license_acceptance
|
534
|
+
FileUtils.mkdir_p(data_path)
|
535
|
+
FileUtils.touch(license_guard_file_path)
|
536
|
+
else
|
537
|
+
log "Please accept the software license agreement to continue."
|
538
|
+
exit(1)
|
539
|
+
end
|
540
|
+
end
|
541
|
+
end
|
542
|
+
|
543
|
+
def ask_license_acceptance
|
544
|
+
log "To use this software, you must agree to the terms of the software license agreement."
|
545
|
+
|
546
|
+
unless STDIN.tty?
|
547
|
+
log "Please view and accept the software license agreement, or pass --accept-license."
|
548
|
+
exit(1)
|
549
|
+
end
|
550
|
+
|
551
|
+
log "Press any key to continue."
|
552
|
+
user_input = STDIN.getch
|
553
|
+
user_input << STDIN.getch while STDIN.ready?
|
554
|
+
# No need to check for user input
|
555
|
+
|
556
|
+
system("less #{project_license_path}")
|
557
|
+
|
558
|
+
loop do
|
559
|
+
log "Type 'yes' to accept the software license agreement, or anything else to cancel."
|
560
|
+
|
561
|
+
user_input = STDIN.gets.chomp.downcase
|
562
|
+
case user_input
|
563
|
+
when "yes"
|
564
|
+
return true
|
565
|
+
else
|
566
|
+
log "You have not accepted the software license agreement."
|
567
|
+
return false
|
568
|
+
end
|
569
|
+
end
|
570
|
+
end
|
571
|
+
|
572
|
+
def project_license_path
|
573
|
+
File.join(base_path, "LICENSE")
|
574
|
+
end
|
575
|
+
|
427
576
|
def tail(*args)
|
428
577
|
# find /var/log -type f -not -path '*/sasl/*' | grep -E -v '(lock|@|tgz|gzip)' | xargs tail --follow=name --retry
|
429
|
-
command = "find #{log_path}"
|
578
|
+
command = "find -L #{log_path}"
|
430
579
|
command << "/#{args[1]}" if args[1]
|
431
|
-
command <<
|
432
|
-
command << log_path_exclude.map { |path| " -not -path #{path}" }.join(
|
580
|
+
command << " -type f"
|
581
|
+
command << log_path_exclude.map { |path| " -not -path '#{path}'" }.join(" ")
|
433
582
|
command << " | grep -E -v '#{log_exclude}' | xargs tail --follow=name --retry"
|
434
583
|
|
435
584
|
system(command)
|
@@ -444,16 +593,17 @@ module Omnibus
|
|
444
593
|
exit_status = 0
|
445
594
|
get_all_services.each do |service_name|
|
446
595
|
next if !service.nil? && service_name != service
|
596
|
+
|
447
597
|
if service_enabled?(service_name)
|
448
|
-
pidfile="#{sv_path}/#{service_name}/supervise/pid"
|
449
|
-
pid=File.read(pidfile).chomp if File.
|
598
|
+
pidfile = "#{sv_path}/#{service_name}/supervise/pid"
|
599
|
+
pid = File.read(pidfile).chomp if File.exist?(pidfile)
|
450
600
|
if pid.nil? || !is_integer?(pid)
|
451
601
|
log "could not find #{service_name} runit pidfile (service already stopped?), cannot attempt SIGKILL..."
|
452
602
|
status = run_command("#{base_path}/init/#{service_name} stop")
|
453
603
|
exit_status = status.exitstatus if exit_status == 0 && !status.success?
|
454
604
|
next
|
455
605
|
end
|
456
|
-
pgrp=get_pgrp_from_pid(pid)
|
606
|
+
pgrp = get_pgrp_from_pid(pid)
|
457
607
|
if pgrp.nil? || !is_integer?(pgrp)
|
458
608
|
log "could not find pgrp of pid #{pid} (not running?), cannot attempt SIGKILL..."
|
459
609
|
status = run_command("#{base_path}/init/#{service_name} stop")
|
@@ -461,8 +611,8 @@ module Omnibus
|
|
461
611
|
next
|
462
612
|
end
|
463
613
|
run_command("#{base_path}/init/#{service_name} stop")
|
464
|
-
pids=get_pids_from_pgrp(pgrp)
|
465
|
-
|
614
|
+
pids = get_pids_from_pgrp(pgrp)
|
615
|
+
unless pids.empty?
|
466
616
|
log "found stuck pids still running in process group: #{pids}, sending SIGKILL" unless pids.empty?
|
467
617
|
sigkill_pgrp(pgrp)
|
468
618
|
end
|
@@ -475,14 +625,14 @@ module Omnibus
|
|
475
625
|
end
|
476
626
|
|
477
627
|
def help(*args)
|
478
|
-
log "#{
|
628
|
+
log "#{exe_name}: command (subcommand)\n"
|
479
629
|
command_map.keys.sort.each do |command|
|
480
630
|
log command
|
481
631
|
log " #{command_map[command][:desc]}"
|
482
632
|
end
|
483
633
|
category_command_map.each do |category, commands|
|
484
634
|
# Remove "-" and replace with spaces in category and capalize for output
|
485
|
-
category_string = category.gsub("-", " ").split.map(&:capitalize).join(
|
635
|
+
category_string = category.gsub("-", " ").split.map(&:capitalize).join(" ")
|
486
636
|
log "#{category_string} Commands:\n"
|
487
637
|
|
488
638
|
# Print each command in this category
|
@@ -491,49 +641,65 @@ module Omnibus
|
|
491
641
|
log " #{commands[command][:desc]}"
|
492
642
|
end
|
493
643
|
end
|
494
|
-
exit
|
644
|
+
# Help is not an error so exit with 0. In cases where we display help as a result of an error
|
645
|
+
# the framework will handle setting proper exit code.
|
646
|
+
exit! 0
|
495
647
|
end
|
496
648
|
|
497
|
-
# Set options
|
498
|
-
#
|
499
|
-
def parse_options
|
500
|
-
args.
|
649
|
+
# Set global options and remove them from the args list we pass
|
650
|
+
# into commands.
|
651
|
+
def parse_options(args)
|
652
|
+
args.select do |option|
|
501
653
|
case option
|
654
|
+
when "--quiet", "-q"
|
655
|
+
@quiet = true
|
656
|
+
false
|
502
657
|
when "--verbose", "-v"
|
503
658
|
@verbose = true
|
659
|
+
false
|
504
660
|
end
|
505
661
|
end
|
506
662
|
end
|
507
663
|
|
508
664
|
# If it begins with a '-', it is an option.
|
509
665
|
def is_option?(arg)
|
510
|
-
arg && arg[0] ==
|
666
|
+
arg && arg[0] == "-"
|
511
667
|
end
|
512
668
|
|
513
669
|
# retrieves the commmand from either the command_map
|
514
670
|
# or the category_command_map, if the command is not found
|
515
671
|
# return nil
|
516
672
|
def retrieve_command(command_to_run)
|
517
|
-
if command_map.
|
673
|
+
if command_map.key?(command_to_run)
|
518
674
|
command_map[command_to_run]
|
519
675
|
else
|
520
676
|
command = nil
|
521
677
|
category_command_map.each do |category, commands|
|
522
|
-
command = commands[command_to_run] if commands.
|
678
|
+
command = commands[command_to_run] if commands.key?(command_to_run)
|
523
679
|
end
|
524
680
|
# return the command, or nil if it wasn't found
|
525
681
|
command
|
526
682
|
end
|
527
683
|
end
|
528
684
|
|
685
|
+
# Previously this would exit immediately with the provided
|
686
|
+
# exit code; however this would prevent post-run hooks from continuing
|
687
|
+
# Instead, we'll just track whether a an exit was requested and use that
|
688
|
+
# to determine how we exit from 'run'
|
529
689
|
def run(args)
|
530
690
|
# Ensure Omnibus related binaries are in the PATH
|
531
691
|
ENV["PATH"] = [File.join(base_path, "bin"),
|
532
|
-
File.join(base_path, "embedded","bin"),
|
533
|
-
ENV[
|
692
|
+
File.join(base_path, "embedded", "bin"),
|
693
|
+
ENV["PATH"]].join(":")
|
534
694
|
|
535
695
|
command_to_run = args[0]
|
536
696
|
|
697
|
+
## when --help is run as the command itself, we need to strip off the
|
698
|
+
## `--` to ensure the command maps correctly.
|
699
|
+
if command_to_run == "--help"
|
700
|
+
command_to_run = "help"
|
701
|
+
end
|
702
|
+
|
537
703
|
# This piece of code checks if the argument is an option. If it is,
|
538
704
|
# then it sets service to nil and adds the argument into the options
|
539
705
|
# argument. This is ugly. A better solution is having a proper parser.
|
@@ -549,31 +715,208 @@ module Omnibus
|
|
549
715
|
service = args[1]
|
550
716
|
end
|
551
717
|
|
552
|
-
# returns either hash content of
|
718
|
+
# returns either hash content of command or nil
|
553
719
|
command = retrieve_command(command_to_run)
|
554
|
-
|
555
720
|
if command.nil?
|
556
721
|
log "I don't know that command."
|
557
722
|
if args.length == 2
|
558
|
-
log "Did you mean: #{
|
723
|
+
log "Did you mean: #{exe_name} #{service} #{command_to_run}?"
|
559
724
|
end
|
560
725
|
help
|
726
|
+
Kernel.exit 1
|
561
727
|
end
|
562
728
|
|
563
729
|
if args.length > 1 && command[:arity] != 2
|
564
730
|
log "The command #{command_to_run} does not accept any arguments"
|
565
|
-
exit
|
731
|
+
Kernel.exit 2
|
566
732
|
end
|
567
733
|
|
568
|
-
parse_options
|
734
|
+
parse_options options
|
735
|
+
@force_exit = false
|
736
|
+
exit_code = 0
|
737
|
+
|
738
|
+
run_global_pre_hooks
|
569
739
|
|
570
|
-
method_to_call = command_to_run.gsub(/-/, '_')
|
571
740
|
# Filter args to just command and service. If you are loading
|
572
741
|
# custom commands and need access to the command line argument,
|
573
742
|
# use ARGV directly.
|
574
743
|
actual_args = [command_to_run, service].reject(&:nil?)
|
575
|
-
|
744
|
+
if command_pre_hook(*actual_args)
|
745
|
+
method_to_call = to_method_name(command_to_run)
|
746
|
+
begin
|
747
|
+
ret = send(method_to_call, *actual_args)
|
748
|
+
rescue SystemExit => e
|
749
|
+
@force_exit = true
|
750
|
+
ret = e.status
|
751
|
+
end
|
752
|
+
command_post_hook(*actual_args)
|
753
|
+
exit_code = ret unless ret.nil?
|
754
|
+
else
|
755
|
+
exit_code = 8
|
756
|
+
@force_exit = true
|
757
|
+
end
|
758
|
+
|
759
|
+
if @force_exit
|
760
|
+
Kernel.exit exit_code
|
761
|
+
else
|
762
|
+
exit_code
|
763
|
+
end
|
764
|
+
end
|
765
|
+
|
766
|
+
def run_global_pre_hooks
|
767
|
+
@global_pre_hooks.each do |hook_name, method_name|
|
768
|
+
|
769
|
+
send(method_name)
|
770
|
+
rescue => e
|
771
|
+
$stderr.puts("Global pre-hook '#{hook_name}' failed with: '#{e.message}'")
|
772
|
+
exit(1)
|
773
|
+
|
774
|
+
end
|
576
775
|
end
|
577
776
|
|
777
|
+
# Below are some basic command hooks that do the right thing
|
778
|
+
# when a service is configured as external via [package][service
|
779
|
+
|
780
|
+
# If a command has a pre-hook defined we will run it.
|
781
|
+
# Otherwise, if it is a run-sv command and the service it refers to
|
782
|
+
# is an external service, we will show an error since we
|
783
|
+
# can't control external services from here.
|
784
|
+
#
|
785
|
+
# If any pre-hook returns false, it will prevent execution of the command
|
786
|
+
# and exit the command with exit code 8.
|
787
|
+
def command_pre_hook(*args)
|
788
|
+
command = args.shift
|
789
|
+
method = to_method_name("#{command}_pre_hook")
|
790
|
+
if respond_to?(method)
|
791
|
+
send(method, *args)
|
792
|
+
else
|
793
|
+
return true if args.empty?
|
794
|
+
|
795
|
+
if SV_COMMAND_NAMES.include? command
|
796
|
+
if service_external? args[0]
|
797
|
+
log error_external_service(command, args[0])
|
798
|
+
return false
|
799
|
+
end
|
800
|
+
end
|
801
|
+
true
|
802
|
+
end
|
803
|
+
end
|
804
|
+
|
805
|
+
# Executes after successful completion of a command
|
806
|
+
# If a post-hook provides a numeric return code, it will
|
807
|
+
# replace the return/exit of the original command
|
808
|
+
def command_post_hook(*args)
|
809
|
+
command = args.shift
|
810
|
+
method = to_method_name("#{command}_post_hook")
|
811
|
+
if respond_to?(method)
|
812
|
+
send(method, *args)
|
813
|
+
end
|
814
|
+
end
|
815
|
+
|
816
|
+
# If we're listing status for all services and have external
|
817
|
+
# services to show, we'll include an output header to show that
|
818
|
+
# we're reporting internal services
|
819
|
+
def status_pre_hook(service = nil)
|
820
|
+
log_internal_service_header if service.nil?
|
821
|
+
true
|
822
|
+
end
|
823
|
+
|
824
|
+
# Status gets its own hook because each externalized service will
|
825
|
+
# have its own things to do in order to report status.
|
826
|
+
# As above, we may also include an output header to show that we're
|
827
|
+
# reporting on external services.
|
828
|
+
#
|
829
|
+
# Your callback for this function should be in the form
|
830
|
+
# 'external_status_#{service_name}(detail_level)
|
831
|
+
# where detail_level is :sparse|:verbose
|
832
|
+
# :sparse is used when it's a summary service status list, eg
|
833
|
+
# "$appname-ctl status"
|
834
|
+
# :verbose is used when the specific service has been named, eg
|
835
|
+
# "$appname-ctl status postgresql"
|
836
|
+
def status_post_hook(service = nil)
|
837
|
+
if service.nil?
|
838
|
+
log_external_service_header
|
839
|
+
external_services.each_key do |service_name|
|
840
|
+
status = send(to_method_name("external_status_#{service_name}"), :sparse)
|
841
|
+
log status
|
842
|
+
end
|
843
|
+
else
|
844
|
+
# Request verbose status if the service is asked for by name.
|
845
|
+
if service_external?(service)
|
846
|
+
status = send(to_method_name("external_status_#{service}"), :verbose)
|
847
|
+
log status
|
848
|
+
end
|
849
|
+
end
|
850
|
+
end
|
851
|
+
|
852
|
+
# Data cleanup requirements for external services aren't met by the standard
|
853
|
+
# 'nuke /var/opt' behavior - this hook allows each service to perform its own
|
854
|
+
# 'cleanse' operations.
|
855
|
+
#
|
856
|
+
# Your callback for this function should be in the
|
857
|
+
# form 'external_cleanup_#{service_name}(do_clean)
|
858
|
+
# where do_cliean is true if the delete should actually be
|
859
|
+
# performed, and false if it's expected to inform the user how to
|
860
|
+
# perform the data cleanup without doing any cleanup itself.
|
861
|
+
def cleanse_post_hook(*args)
|
862
|
+
external_services.each_key do |service_name|
|
863
|
+
perform_delete = ARGV.include?("--with-external")
|
864
|
+
if perform_delete
|
865
|
+
log "Deleting data from external service: #{service_name}"
|
866
|
+
end
|
867
|
+
send(to_method_name("external_cleanse_#{service_name}"), perform_delete)
|
868
|
+
end
|
869
|
+
end
|
870
|
+
|
871
|
+
# Add some output headers if we have external services enabled
|
872
|
+
def service_list_pre_hook
|
873
|
+
log_internal_service_header
|
874
|
+
true
|
875
|
+
end
|
876
|
+
|
877
|
+
# Capture external services in the output list as well.
|
878
|
+
def service_list_post_hook
|
879
|
+
log_external_service_header
|
880
|
+
external_services.each do |name, settings|
|
881
|
+
log " > #{name} on #{settings["vip"]}"
|
882
|
+
end
|
883
|
+
end
|
884
|
+
|
885
|
+
def error_external_service(command, service)
|
886
|
+
<<EOM
|
887
|
+
-------------------------------------------------------------------
|
888
|
+
The service #{service} is running externally and cannot be managed
|
889
|
+
vi chef-server-ctl. Please log into #{external_services[service]["vip"]}
|
890
|
+
to manage it directly.
|
891
|
+
-------------------------------------------------------------------
|
892
|
+
EOM
|
893
|
+
end
|
894
|
+
|
895
|
+
def format_multiline_message(indent, message)
|
896
|
+
if message.class == String
|
897
|
+
message = message.split("\n")
|
898
|
+
end
|
899
|
+
spaces = " " * indent
|
900
|
+
message.map! { |line| "#{spaces}#{line.strip}" }
|
901
|
+
message.join("\n")
|
902
|
+
end
|
903
|
+
|
904
|
+
def log_internal_service_header
|
905
|
+
# Don't decorate output unless we have
|
906
|
+
# external services to report on.
|
907
|
+
return if external_services.empty?
|
908
|
+
|
909
|
+
log "-------------------"
|
910
|
+
log " Internal Services "
|
911
|
+
log "-------------------"
|
912
|
+
end
|
913
|
+
|
914
|
+
def log_external_service_header
|
915
|
+
return if external_services.empty?
|
916
|
+
|
917
|
+
log "-------------------"
|
918
|
+
log " External Services "
|
919
|
+
log "-------------------"
|
920
|
+
end
|
578
921
|
end
|
579
922
|
end
|