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