gitlab-omnibus-ctl 0.6.13

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f995169a2d2b7f4de234a0133810a8ef4c7a5a4a34bf6ffc3621f1b7490ce1aa
4
+ data.tar.gz: 9abb7fcee800c82d11c5d6afda09c3a68da34c2a669b3c8878279fd961918066
5
+ SHA512:
6
+ metadata.gz: d62e022f85e323b9f4516c89c6c6b2c353ee9fd185da2ac17e1d3d412447e976652f914180724f6aec64c38fecfa027f15411248997169a72341ef2abf2d8fbb
7
+ data.tar.gz: 124db712e05e00c1e6b4f20740d6ecc6af03970d2e0e2a0c1552791a7162bbdfbdce93dcb6c3978737318cd59d5d58bb0d6948330deb1363284e9f17bb596045
data/LICENSE ADDED
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
data/bin/omnibus-ctl ADDED
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/ruby
2
+ #
3
+ # Author:: Adam Jacob (<adam@opscode.com>)
4
+ # Copyright:: Copyright (c) 2011 Opscode, Inc.
5
+ # License:: Apache License, Version 2.0
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+
20
+ require "omnibus-ctl"
21
+
22
+ # service name: ARGV[0]
23
+ # additional modules: ARGV[1]
24
+ # command: ARGV[2]
25
+ # service: ARGV[3]
26
+ # options: ARGV[4..]
27
+
28
+ ctl = Omnibus::Ctl.new(ARGV[0])
29
+ ctl.load_files(ARGV[1])
30
+ arguments = ARGV[2..-1] # Get the rest of the command line arguments
31
+ ctl.run(arguments)
32
+ exit 0
33
+
@@ -0,0 +1,5 @@
1
+ module Omnibus
2
+ class Ctl
3
+ VERSION = "0.6.13".freeze
4
+ end
5
+ end
@@ -0,0 +1,923 @@
1
+ # Copyright (c) 2012-2015 Chef Software, Inc.
2
+ # License:: Apache License, Version 2.0
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require "omnibus-ctl/version"
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"
25
+
26
+ module Omnibus
27
+ class Ctl
28
+
29
+ File.umask(022)
30
+
31
+ SV_COMMAND_NAMES = %w{status up down once pause cont hup alarm int quit
32
+ term kill start stop restart shutdown force-stop
33
+ force-reload force-restart force-shutdown check usr1 usr2}.freeze
34
+
35
+ attr_accessor :name, :display_name, :log_exclude, :base_path, :sv_path,
36
+ :service_path, :etc_path, :data_path, :log_path, :command_map, :category_command_map,
37
+ :fh_output, :kill_users, :verbose, :log_path_exclude
38
+
39
+ attr_reader :backup_dir, :exe_name
40
+
41
+ def initialize(name, merge_service_commands = true, disp_name = nil)
42
+ @name = name
43
+ @service_commands = merge_service_commands
44
+ @display_name = disp_name || name
45
+ @base_path = "/opt/#{name}"
46
+ @sv_path = File.join(@base_path, "sv")
47
+ @service_path = File.join(@base_path, "service")
48
+ @log_path = "/var/log/#{name}"
49
+ @data_path = "/var/opt/#{name}"
50
+ @etc_path = "/etc/#{name}"
51
+ @log_exclude = "(config|lock|@|bz2|gz|gzip|tbz2|tgz|txz|xz|zip)"
52
+ @log_path_exclude = ["*/sasl/*"]
53
+ @fh_output = STDOUT
54
+ @kill_users = []
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
+
66
+ # backwards compat command map that does not have categories
67
+ @command_map = {}
68
+
69
+ # categoired commands that we want by default
70
+ @category_command_map = {
71
+ "general" => {
72
+ "show-config" => {
73
+ desc: "Show the configuration that would be generated by reconfigure.",
74
+ arity: 1,
75
+ },
76
+ "reconfigure" => {
77
+ desc: "Reconfigure the application.",
78
+ arity: 2,
79
+ },
80
+ "cleanse" => {
81
+ desc: "Delete *all* #{display_name} data, and start from scratch.",
82
+ arity: 2,
83
+ },
84
+ "uninstall" => {
85
+ arity: 1,
86
+ desc: "Kill all processes and uninstall the process supervisor (data will be preserved).",
87
+ },
88
+ "help" => {
89
+ arity: 1,
90
+ desc: "Print this help message.",
91
+ },
92
+ },
93
+ }
94
+ service_command_map = {
95
+ "service-management" => {
96
+ "service-list" => {
97
+ arity: 1,
98
+ desc: "List all the services (enabled services appear with a *.)",
99
+ },
100
+ "status" => {
101
+ desc: "Show the status of all the services.",
102
+ arity: 2,
103
+ },
104
+ "tail" => {
105
+ desc: "Watch the service logs of all enabled services.",
106
+ arity: 2,
107
+ },
108
+ "start" => {
109
+ desc: "Start services if they are down, and restart them if they stop.",
110
+ arity: 2,
111
+ },
112
+ "stop" => {
113
+ desc: "Stop the services, and do not restart them.",
114
+ arity: 2,
115
+ },
116
+ "restart" => {
117
+ desc: "Stop the services if they are running, then start them again.",
118
+ arity: 2,
119
+ },
120
+ "once" => {
121
+ desc: "Start the services if they are down. Do not restart them if they stop.",
122
+ arity: 2,
123
+ },
124
+ "hup" => {
125
+ desc: "Send the services a HUP.",
126
+ arity: 2,
127
+ },
128
+ "term" => {
129
+ desc: "Send the services a TERM.",
130
+ arity: 2,
131
+ },
132
+ "int" => {
133
+ desc: "Send the services an INT.",
134
+ arity: 2,
135
+ },
136
+ "kill" => {
137
+ desc: "Send the services a KILL.",
138
+ arity: 2,
139
+ },
140
+ "graceful-kill" => {
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
+ },
153
+ }
154
+ @category_command_map.merge!(service_command_map) if service_commands?
155
+ end
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
+
165
+ SV_COMMAND_NAMES.each do |sv_cmd|
166
+ define_method to_method_name(sv_cmd) do |*args|
167
+ run_sv_command(*args)
168
+ end
169
+ end
170
+
171
+ # merges category_command_map and command_map,
172
+ # removing categories
173
+ def get_all_commands_hash
174
+ without_categories = {}
175
+ category_command_map.each do |category, commands|
176
+ without_categories.merge!(commands)
177
+ end
178
+ command_map.merge(without_categories)
179
+ end
180
+
181
+ def service_commands?
182
+ @service_commands
183
+ end
184
+
185
+ def load_files(path)
186
+ Dir["#{path}/*.rb"].each do |file|
187
+ load_file(file)
188
+ end
189
+ end
190
+
191
+ def load_file(filepath)
192
+ eval(IO.read(filepath), nil, filepath, 1) # rubocop: disable Security/Eval
193
+ end
194
+
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) }
198
+ end
199
+
200
+ def add_command_under_category(name, category, description, arity = 1, &block)
201
+ # add new category if it doesn't exist
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) }
205
+ end
206
+
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
216
+ end
217
+
218
+ def log(msg)
219
+ fh_output.puts msg
220
+ end
221
+
222
+ def get_pgrp_from_pid(pid)
223
+ ps = `which ps`.chomp
224
+ `#{ps} -p #{pid} -o pgrp=`.chomp
225
+ end
226
+
227
+ def get_pids_from_pgrp(pgrp)
228
+ pgrep = `which pgrep`.chomp
229
+ `#{pgrep} -g #{pgrp}`.split(/\n/).join(" ")
230
+ end
231
+
232
+ def sigkill_pgrp(pgrp)
233
+ pkill = `which pkill`.chomp
234
+ run_command("#{pkill} -9 -g #{pgrp}")
235
+ end
236
+
237
+ def run_command(command)
238
+ system(command)
239
+ $?
240
+ end
241
+
242
+ def service_list(*args)
243
+ get_all_services.each do |service_name|
244
+ print "#{service_name}"
245
+ print "*" if service_enabled?(service_name)
246
+ print "\n"
247
+ end
248
+ exit! 0
249
+ end
250
+
251
+ def cleanup_procs_and_nuke(filestr, calling_method = nil)
252
+ run_sv_command("stop")
253
+
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")
256
+ run_command("kill -1 1")
257
+
258
+ @backup_dir = Time.now.strftime("/root/#{name}-cleanse-%FT%R")
259
+
260
+ FileUtils.mkdir_p("/root") unless File.exist?("/root")
261
+ FileUtils.rm_rf(backup_dir)
262
+ FileUtils.cp_r(etc_path, backup_dir) if File.exist?(etc_path)
263
+ run_command("rm -rf #{filestr}")
264
+ graceful_kill
265
+
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
268
+ run_command("pkill -HUP -f 'runsvdir -P #{service_path}'")
269
+ sleep 3
270
+ run_command("pkill -TERM -u #{kill_users.join(",")}") if kill_users.length > 0
271
+ run_command("pkill -TERM -f 'runsvdir -P #{service_path}'")
272
+ sleep 3
273
+ run_command("pkill -KILL -u #{kill_users.join(",")}") if kill_users.length > 0
274
+ run_command("pkill -KILL -f 'runsvdir -P #{service_path}'")
275
+
276
+ get_all_services.each do |die_daemon_die|
277
+ run_command("pkill -KILL -f 'runsv #{die_daemon_die}'")
278
+ end
279
+ log "Your config files have been backed up to #{backup_dir}."
280
+ exit! 0
281
+ end
282
+
283
+ def uninstall(*args)
284
+ cleanup_procs_and_nuke("/tmp/opt")
285
+ end
286
+
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
327
+ end
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")
333
+ end
334
+
335
+ def get_all_services_files
336
+ Dir[File.join(sv_path, "*")]
337
+ end
338
+
339
+ def get_all_services
340
+ get_all_services_files.map { |f| File.basename(f) }.sort
341
+ end
342
+
343
+ def service_enabled?(service_name)
344
+ File.symlink?("#{service_path}/#{service_name}")
345
+ end
346
+
347
+ def run_sv_command(sv_cmd, service = nil)
348
+ exit_status = 0
349
+ sv_cmd = "1" if sv_cmd == "usr1"
350
+ sv_cmd = "2" if sv_cmd == "usr2"
351
+ if service
352
+ exit_status += run_sv_command_for_service(sv_cmd, service)
353
+ else
354
+ get_all_services.each do |service_name|
355
+ exit_status += run_sv_command_for_service(sv_cmd, service_name) if global_service_command_permitted(sv_cmd, service_name)
356
+ end
357
+ end
358
+ exit! exit_status
359
+ end
360
+
361
+ # run an sv command for a specific service name
362
+ def run_sv_command_for_service(sv_cmd, service_name)
363
+ if service_enabled?(service_name)
364
+ status = run_command("#{base_path}/init/#{service_name} #{sv_cmd}")
365
+ status.exitstatus
366
+ else
367
+ log "#{service_name} disabled" if sv_cmd == "status" && verbose
368
+ 0
369
+ end
370
+ end
371
+
372
+ # if we're running a global service command (like p-c-c status)
373
+ # across all of the services, there are certain cases where we
374
+ # want to prevent services files that exist in the service
375
+ # directory from being activated. This method is the logic that
376
+ # blocks those services
377
+ def global_service_command_permitted(sv_cmd, service_name)
378
+ # For services that have been removed, we only want to
379
+ # them to respond to the stop command. They should not show
380
+ # up in status, and they should not be started.
381
+ if removed_services.include?(service_name)
382
+ return sv_cmd == "stop"
383
+ end
384
+
385
+ # For keepalived, we only want it to respond to the status
386
+ # command when running global service commands like p-c-c start
387
+ # and p-c-c stop
388
+ if service_name == "keepalived"
389
+ return sv_cmd == "status"
390
+ end
391
+
392
+ # If c-s-c status is called, check to see if the service
393
+ # is hidden supposed to be hidden from the status results
394
+ # (mover for example should be hidden).
395
+ if sv_cmd == "status"
396
+ return !(hidden_services.include?(service_name))
397
+ end
398
+
399
+ # All other services respond normally to p-c-c * commands
400
+ true
401
+ end
402
+
403
+ # removed services are configured via the attributes file in
404
+ # the main omnibus cookbook
405
+ def removed_services
406
+ # in the case that there is no running_config (the config file does
407
+ # not exist), we know that this will be a new server, and we don't
408
+ # have to worry about pre-upgrade services hanging around. We can safely
409
+ # return an empty array when running_config is nil
410
+ running_package_config["removed_services"] || []
411
+ end
412
+
413
+ # hidden services are configured via the attributes file in
414
+ # the main omnibus cookbook
415
+ #
416
+ # hidden services are services that we do not want to show up in
417
+ # c-s-c status.
418
+ def hidden_services
419
+ # in the case that there is no running_config (the config file does
420
+ # not exist), we don't want to return nil, just return an empty array.
421
+ # worse result with doing that is services that we don't want to show up in
422
+ # c-s-c status will show up.
423
+ running_package_config["hidden_services"] || []
424
+ end
425
+
426
+ # translate the name from the config to the package name.
427
+ # this is a special case for the private-chef package because
428
+ # it is configured to use the name and directory structure of
429
+ # 'opscode', not 'private-chef'
430
+ def package_name
431
+ case @name
432
+ # The "opscode" in /opt/opscode
433
+ when ::ChefUtils::Dist::Org::LEGACY_CONF_DIR
434
+ "private-chef"
435
+ else
436
+ @name
437
+ end
438
+ end
439
+
440
+ # returns nil when chef-server-running.json does not exist
441
+ def running_config
442
+ fname = "#{etc_path}/#{::ChefUtils::Dist::Server::SERVER}-running.json"
443
+ @running_config ||= if File.exist?(fname)
444
+ JSON.parse(File.read(fname))
445
+ end
446
+ end
447
+
448
+ # Helper function that returns the hash of config hashes that have the key 'external' : true
449
+ # in the running config. If none exist it will return an empty hash.
450
+ def external_services
451
+ @external_services ||= running_package_config.select { |k, v| v.class == Hash and v["external"] == true }
452
+ end
453
+
454
+ # Helper function that returns true if an external service entry exists for
455
+ # the named service
456
+ def service_external?(service)
457
+ return false if service.nil?
458
+
459
+ external_services.key? service
460
+ end
461
+
462
+ # Gives package config from the running_config.
463
+ # If there is no running config or if package_name doens't
464
+ # reference a valid key, this will return an empty hash
465
+ def running_package_config
466
+ if (cfg = running_config)
467
+ cfg[package_name.gsub(/-/, "_")] || {}
468
+ else
469
+ {}
470
+ end
471
+ end
472
+
473
+ # This returns running_config[package][service].
474
+ #
475
+ # If there is no running_config or is no matching key
476
+ # it will return nil.
477
+ def running_service_config(service)
478
+ running_package_config[service]
479
+ end
480
+
481
+ def remove_old_node_state
482
+ node_cache_path = "#{base_path}/embedded/nodes/"
483
+ status = run_command("rm -rf #{node_cache_path}")
484
+ unless status.success?
485
+ log "Could not remove cached node state!"
486
+ exit 1
487
+ end
488
+ end
489
+
490
+ def run_chef(attr_location, args = "")
491
+ if @verbose
492
+ log_level = "-l debug"
493
+ elsif @quiet
494
+ # null formatter is awfully quiet, so let them know we're doing something.
495
+ log "Reconfiguring #{display_name}."
496
+ log_level = "-l fatal -F null"
497
+ else
498
+ log_level = ""
499
+ end
500
+ remove_old_node_state
501
+ cmd = "#{base_path}/embedded/bin/cinc-client #{log_level} -z -c #{base_path}/embedded/cookbooks/solo.rb -j #{attr_location}"
502
+ cmd += " #{args}" unless args.empty?
503
+ run_command(cmd)
504
+ end
505
+
506
+ def show_config(*args)
507
+ status = run_chef("#{base_path}/embedded/cookbooks/show-config.json", "-l fatal -F null")
508
+ exit! status.success? ? 0 : 1
509
+ end
510
+
511
+ def reconfigure(*args)
512
+ # args being passed to this command does not include the ones that are
513
+ # starting with "-". See #is_option? method. If it is starting with "-"
514
+ # then it is treated as a option and we need to look for them in ARGV.
515
+ check_license_acceptance(ARGV.include?("--accept-license"))
516
+
517
+ status = run_chef("#{base_path}/embedded/cookbooks/dna.json")
518
+ if status.success?
519
+ log "#{display_name} Reconfigured!"
520
+ exit! 0
521
+ else
522
+ exit! 1
523
+ end
524
+ end
525
+
526
+ def check_license_acceptance(override_accept = false)
527
+ license_guard_file_path = File.join(data_path, ".license.accepted")
528
+
529
+ # If the project does not have a license we do not have
530
+ # any license to accept.
531
+ return unless File.exist?(project_license_path)
532
+
533
+ unless File.exist?(license_guard_file_path)
534
+ if override_accept || ask_license_acceptance
535
+ FileUtils.mkdir_p(data_path)
536
+ FileUtils.touch(license_guard_file_path)
537
+ else
538
+ log "Please accept the software license agreement to continue."
539
+ exit(1)
540
+ end
541
+ end
542
+ end
543
+
544
+ def ask_license_acceptance
545
+ log "To use this software, you must agree to the terms of the software license agreement."
546
+
547
+ unless STDIN.tty?
548
+ log "Please view and accept the software license agreement, or pass --accept-license."
549
+ exit(1)
550
+ end
551
+
552
+ log "Press any key to continue."
553
+ user_input = STDIN.getch
554
+ user_input << STDIN.getch while STDIN.ready?
555
+ # No need to check for user input
556
+
557
+ system("less #{project_license_path}")
558
+
559
+ loop do
560
+ log "Type 'yes' to accept the software license agreement, or anything else to cancel."
561
+
562
+ user_input = STDIN.gets.chomp.downcase
563
+ case user_input
564
+ when "yes"
565
+ return true
566
+ else
567
+ log "You have not accepted the software license agreement."
568
+ return false
569
+ end
570
+ end
571
+ end
572
+
573
+ def project_license_path
574
+ File.join(base_path, "LICENSE")
575
+ end
576
+
577
+ def tail(*args)
578
+ # find /var/log -type f -not -path '*/sasl/*' | grep -E -v '(lock|@|tgz|gzip)' | xargs tail --follow=name --retry
579
+ command = "find -L #{log_path}"
580
+ command << "/#{args[1]}" if args[1]
581
+ command << " -type f"
582
+ command << log_path_exclude.map { |path| " -not -path '#{path}'" }.join(" ")
583
+ command << " | grep -E -v '#{log_exclude}' | xargs tail --follow=name --retry"
584
+
585
+ system(command)
586
+ end
587
+
588
+ def is_integer?(string)
589
+ return true if Integer(string) rescue false
590
+ end
591
+
592
+ def graceful_kill(*args)
593
+ service = args[1]
594
+ exit_status = 0
595
+ get_all_services.each do |service_name|
596
+ next if !service.nil? && service_name != service
597
+
598
+ if service_enabled?(service_name)
599
+ pidfile = "#{sv_path}/#{service_name}/supervise/pid"
600
+ pid = File.read(pidfile).chomp if File.exist?(pidfile)
601
+ if pid.nil? || !is_integer?(pid)
602
+ log "could not find #{service_name} runit pidfile (service already stopped?), cannot attempt SIGKILL..."
603
+ status = run_command("#{base_path}/init/#{service_name} stop")
604
+ exit_status = status.exitstatus if exit_status == 0 && !status.success?
605
+ next
606
+ end
607
+ pgrp = get_pgrp_from_pid(pid)
608
+ if pgrp.nil? || !is_integer?(pgrp)
609
+ log "could not find pgrp of pid #{pid} (not running?), cannot attempt SIGKILL..."
610
+ status = run_command("#{base_path}/init/#{service_name} stop")
611
+ exit_status = status.exitstatus if exit_status == 0 && !status.success?
612
+ next
613
+ end
614
+ run_command("#{base_path}/init/#{service_name} stop")
615
+ pids = get_pids_from_pgrp(pgrp)
616
+ unless pids.empty?
617
+ log "found stuck pids still running in process group: #{pids}, sending SIGKILL" unless pids.empty?
618
+ sigkill_pgrp(pgrp)
619
+ end
620
+ else
621
+ log "#{service_name} disabled, not stopping"
622
+ exit_status = 1
623
+ end
624
+ end
625
+ exit! exit_status
626
+ end
627
+
628
+ def help(*args)
629
+ log "#{exe_name}: command (subcommand)\n"
630
+ command_map.keys.sort.each do |command|
631
+ log command
632
+ log " #{command_map[command][:desc]}"
633
+ end
634
+ category_command_map.each do |category, commands|
635
+ # Remove "-" and replace with spaces in category and capalize for output
636
+ category_string = category.gsub("-", " ").split.map(&:capitalize).join(" ")
637
+ log "#{category_string} Commands:\n"
638
+
639
+ # Print each command in this category
640
+ commands.keys.sort.each do |command|
641
+ log " #{command}"
642
+ log " #{commands[command][:desc]}"
643
+ end
644
+ end
645
+ # Help is not an error so exit with 0. In cases where we display help as a result of an error
646
+ # the framework will handle setting proper exit code.
647
+ exit! 0
648
+ end
649
+
650
+ # Set global options and remove them from the args list we pass
651
+ # into commands.
652
+ def parse_options(args)
653
+ args.select do |option|
654
+ case option
655
+ when "--quiet", "-q"
656
+ @quiet = true
657
+ false
658
+ when "--verbose", "-v"
659
+ @verbose = true
660
+ false
661
+ end
662
+ end
663
+ end
664
+
665
+ # If it begins with a '-', it is an option.
666
+ def is_option?(arg)
667
+ arg && arg[0] == "-"
668
+ end
669
+
670
+ # retrieves the commmand from either the command_map
671
+ # or the category_command_map, if the command is not found
672
+ # return nil
673
+ def retrieve_command(command_to_run)
674
+ if command_map.key?(command_to_run)
675
+ command_map[command_to_run]
676
+ else
677
+ command = nil
678
+ category_command_map.each do |category, commands|
679
+ command = commands[command_to_run] if commands.key?(command_to_run)
680
+ end
681
+ # return the command, or nil if it wasn't found
682
+ command
683
+ end
684
+ end
685
+
686
+ # Previously this would exit immediately with the provided
687
+ # exit code; however this would prevent post-run hooks from continuing
688
+ # Instead, we'll just track whether a an exit was requested and use that
689
+ # to determine how we exit from 'run'
690
+ def run(args)
691
+ # Ensure Omnibus related binaries are in the PATH
692
+ ENV["PATH"] = [File.join(base_path, "bin"),
693
+ File.join(base_path, "embedded", "bin"),
694
+ ENV["PATH"]].join(":")
695
+
696
+ command_to_run = args[0]
697
+
698
+ ## when --help is run as the command itself, we need to strip off the
699
+ ## `--` to ensure the command maps correctly.
700
+ if command_to_run == "--help"
701
+ command_to_run = "help"
702
+ end
703
+
704
+ # This piece of code checks if the argument is an option. If it is,
705
+ # then it sets service to nil and adds the argument into the options
706
+ # argument. This is ugly. A better solution is having a proper parser.
707
+ # But if we are going to implement a proper parser, we might as well
708
+ # port this to Thor rather than reinventing Thor. For now, this preserves
709
+ # the behavior to complain and exit with an error if one attempts to invoke
710
+ # a pcc command that does not accept an argument. Like "help".
711
+ options = args[2..-1] || []
712
+ if is_option?(args[1])
713
+ options.unshift(args[1])
714
+ service = nil
715
+ else
716
+ service = args[1]
717
+ end
718
+
719
+ # returns either hash content of command or nil
720
+ command = retrieve_command(command_to_run)
721
+ if command.nil?
722
+ log "I don't know that command."
723
+ if args.length == 2
724
+ log "Did you mean: #{exe_name} #{service} #{command_to_run}?"
725
+ end
726
+ help
727
+ Kernel.exit 1
728
+ end
729
+
730
+ if args.length > 1 && command[:arity] != 2
731
+ log "The command #{command_to_run} does not accept any arguments"
732
+ Kernel.exit 2
733
+ end
734
+
735
+ parse_options options
736
+ @force_exit = false
737
+ exit_code = 0
738
+
739
+ run_global_pre_hooks
740
+
741
+ # Filter args to just command and service. If you are loading
742
+ # custom commands and need access to the command line argument,
743
+ # use ARGV directly.
744
+ actual_args = [command_to_run, service].reject(&:nil?)
745
+ if command_pre_hook(*actual_args)
746
+ method_to_call = to_method_name(command_to_run)
747
+ begin
748
+ ret = send(method_to_call, *actual_args)
749
+ rescue SystemExit => e
750
+ @force_exit = true
751
+ ret = e.status
752
+ end
753
+ command_post_hook(*actual_args)
754
+ exit_code = ret unless ret.nil?
755
+ else
756
+ exit_code = 8
757
+ @force_exit = true
758
+ end
759
+
760
+ if @force_exit
761
+ Kernel.exit exit_code
762
+ else
763
+ exit_code
764
+ end
765
+ end
766
+
767
+ def run_global_pre_hooks
768
+ @global_pre_hooks.each do |hook_name, method_name|
769
+
770
+ send(method_name)
771
+ rescue => e
772
+ $stderr.puts("Global pre-hook '#{hook_name}' failed with: '#{e.message}'")
773
+ exit(1)
774
+
775
+ end
776
+ end
777
+
778
+ # Below are some basic command hooks that do the right thing
779
+ # when a service is configured as external via [package][service
780
+
781
+ # If a command has a pre-hook defined we will run it.
782
+ # Otherwise, if it is a run-sv command and the service it refers to
783
+ # is an external service, we will show an error since we
784
+ # can't control external services from here.
785
+ #
786
+ # If any pre-hook returns false, it will prevent execution of the command
787
+ # and exit the command with exit code 8.
788
+ def command_pre_hook(*args)
789
+ command = args.shift
790
+ method = to_method_name("#{command}_pre_hook")
791
+ if respond_to?(method)
792
+ send(method, *args)
793
+ else
794
+ return true if args.empty?
795
+
796
+ if SV_COMMAND_NAMES.include? command
797
+ if service_external? args[0]
798
+ log error_external_service(command, args[0])
799
+ return false
800
+ end
801
+ end
802
+ true
803
+ end
804
+ end
805
+
806
+ # Executes after successful completion of a command
807
+ # If a post-hook provides a numeric return code, it will
808
+ # replace the return/exit of the original command
809
+ def command_post_hook(*args)
810
+ command = args.shift
811
+ method = to_method_name("#{command}_post_hook")
812
+ if respond_to?(method)
813
+ send(method, *args)
814
+ end
815
+ end
816
+
817
+ # If we're listing status for all services and have external
818
+ # services to show, we'll include an output header to show that
819
+ # we're reporting internal services
820
+ def status_pre_hook(service = nil)
821
+ log_internal_service_header if service.nil?
822
+ true
823
+ end
824
+
825
+ # Status gets its own hook because each externalized service will
826
+ # have its own things to do in order to report status.
827
+ # As above, we may also include an output header to show that we're
828
+ # reporting on external services.
829
+ #
830
+ # Your callback for this function should be in the form
831
+ # 'external_status_#{service_name}(detail_level)
832
+ # where detail_level is :sparse|:verbose
833
+ # :sparse is used when it's a summary service status list, eg
834
+ # "$appname-ctl status"
835
+ # :verbose is used when the specific service has been named, eg
836
+ # "$appname-ctl status postgresql"
837
+ def status_post_hook(service = nil)
838
+ if service.nil?
839
+ log_external_service_header
840
+ external_services.each_key do |service_name|
841
+ status = send(to_method_name("external_status_#{service_name}"), :sparse)
842
+ log status
843
+ end
844
+ else
845
+ # Request verbose status if the service is asked for by name.
846
+ if service_external?(service)
847
+ status = send(to_method_name("external_status_#{service}"), :verbose)
848
+ log status
849
+ end
850
+ end
851
+ end
852
+
853
+ # Data cleanup requirements for external services aren't met by the standard
854
+ # 'nuke /var/opt' behavior - this hook allows each service to perform its own
855
+ # 'cleanse' operations.
856
+ #
857
+ # Your callback for this function should be in the
858
+ # form 'external_cleanup_#{service_name}(do_clean)
859
+ # where do_cliean is true if the delete should actually be
860
+ # performed, and false if it's expected to inform the user how to
861
+ # perform the data cleanup without doing any cleanup itself.
862
+ def cleanse_post_hook(*args)
863
+ external_services.each_key do |service_name|
864
+ perform_delete = ARGV.include?("--with-external")
865
+ if perform_delete
866
+ log "Deleting data from external service: #{service_name}"
867
+ end
868
+ send(to_method_name("external_cleanse_#{service_name}"), perform_delete)
869
+ end
870
+ end
871
+
872
+ # Add some output headers if we have external services enabled
873
+ def service_list_pre_hook
874
+ log_internal_service_header
875
+ true
876
+ end
877
+
878
+ # Capture external services in the output list as well.
879
+ def service_list_post_hook
880
+ log_external_service_header
881
+ external_services.each do |name, settings|
882
+ log " > #{name} on #{settings["vip"]}"
883
+ end
884
+ end
885
+
886
+ def error_external_service(command, service)
887
+ <<EOM
888
+ -------------------------------------------------------------------
889
+ The service #{service} is running externally and cannot be managed
890
+ vi chef-server-ctl. Please log into #{external_services[service]["vip"]}
891
+ to manage it directly.
892
+ -------------------------------------------------------------------
893
+ EOM
894
+ end
895
+
896
+ def format_multiline_message(indent, message)
897
+ if message.class == String
898
+ message = message.split("\n")
899
+ end
900
+ spaces = " " * indent
901
+ message.map! { |line| "#{spaces}#{line.strip}" }
902
+ message.join("\n")
903
+ end
904
+
905
+ def log_internal_service_header
906
+ # Don't decorate output unless we have
907
+ # external services to report on.
908
+ return if external_services.empty?
909
+
910
+ log "-------------------"
911
+ log " Internal Services "
912
+ log "-------------------"
913
+ end
914
+
915
+ def log_external_service_header
916
+ return if external_services.empty?
917
+
918
+ log "-------------------"
919
+ log " External Services "
920
+ log "-------------------"
921
+ end
922
+ end
923
+ end
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gitlab-omnibus-ctl
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.6.13
5
+ platform: ruby
6
+ authors:
7
+ - Chef Software, Inc.
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-07-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: chef-utils
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 16.5.54
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 16.5.54
27
+ - !ruby/object:Gem::Dependency
28
+ name: chefstyle
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '='
32
+ - !ruby/object:Gem::Version
33
+ version: 2.2.0
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '='
39
+ - !ruby/object:Gem::Version
40
+ version: 2.2.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.2'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.2'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec_junit_formatter
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: Provides command line control for omnibus pakcages, rarely used as a
84
+ gem
85
+ email:
86
+ - legal@chef.io
87
+ executables:
88
+ - omnibus-ctl
89
+ extensions: []
90
+ extra_rdoc_files: []
91
+ files:
92
+ - LICENSE
93
+ - bin/omnibus-ctl
94
+ - lib/omnibus-ctl.rb
95
+ - lib/omnibus-ctl/version.rb
96
+ homepage: https://gitlab.com/gitlab-org/build/omnibus-mirror/omnibus-ctl
97
+ licenses:
98
+ - Apache-2.0
99
+ metadata: {}
100
+ post_install_message:
101
+ rdoc_options: []
102
+ require_paths:
103
+ - lib
104
+ required_ruby_version: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ version: '2.6'
109
+ required_rubygems_version: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: '0'
114
+ requirements: []
115
+ rubygems_version: 3.5.15
116
+ signing_key:
117
+ specification_version: 4
118
+ summary: Provides command line control for omnibus packages
119
+ test_files: []