gitlab-omnibus-ctl 0.6.13

Sign up to get free protection for your applications and to get access to all the features.
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: []