process_bot 0.1.1 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -1
- data/.ruby-version +1 -0
- data/Gemfile +7 -2
- data/Gemfile.lock +54 -28
- data/README.md +19 -9
- data/exe/process_bot +34 -0
- data/lib/process_bot/capistrano/sidekiq.rake +14 -25
- data/lib/process_bot/capistrano/sidekiq.rb +0 -2
- data/lib/process_bot/capistrano/sidekiq_helpers.rb +42 -26
- data/lib/process_bot/capistrano.rb +1 -0
- data/lib/process_bot/client_socket.rb +31 -0
- data/lib/process_bot/control_socket.rb +31 -10
- data/lib/process_bot/logger.rb +20 -8
- data/lib/process_bot/options.rb +57 -2
- data/lib/process_bot/process/handlers/sidekiq.rb +12 -22
- data/lib/process_bot/process/runner.rb +58 -6
- data/lib/process_bot/process.rb +114 -13
- data/lib/process_bot/version.rb +1 -1
- data/lib/process_bot.rb +1 -0
- data/peak_flow.yml +19 -3
- data/process_bot.gemspec +3 -11
- metadata +13 -51
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b719a497be535319bf027f1669321e58466f845657bdebb82f94d78f6d6e984f
|
4
|
+
data.tar.gz: cbbbc984e75edacbcdf6e9f8579633e6ebac87cf4e0690b944a7e32b4424c4f1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fdbd9fd4a6196cf6d9183289ec7c9ee984d25e5e2af0af5c8213a5fe649865687aa70f0dcf5a49f91100c7ceae72298e9bb9bdcb9dac9861c86b50209528d1a6
|
7
|
+
data.tar.gz: 97942320a0a9354770aa8f4e155a620a1c232c5fd712d2269f525d76b1a00adc2480c6c8e51c79c8eb2047a391224c6597e4c9bc74bf4b3369aa0664b7418e1d
|
data/.rubocop.yml
CHANGED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.7.8
|
data/Gemfile
CHANGED
@@ -5,9 +5,14 @@ source "https://rubygems.org"
|
|
5
5
|
# Specify your gem's dependencies in process_bot.gemspec
|
6
6
|
gemspec
|
7
7
|
|
8
|
+
gem "pry"
|
8
9
|
gem "rake"
|
9
10
|
gem "rspec"
|
10
|
-
gem "rubocop"
|
11
11
|
gem "string-cases"
|
12
12
|
|
13
|
-
|
13
|
+
group :development do
|
14
|
+
gem "rubocop"
|
15
|
+
gem "rubocop-performance"
|
16
|
+
gem "rubocop-rake"
|
17
|
+
gem "rubocop-rspec"
|
18
|
+
end
|
data/Gemfile.lock
CHANGED
@@ -1,63 +1,89 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
process_bot (0.1.
|
4
|
+
process_bot (0.1.3)
|
5
|
+
knjrbfw (>= 0.0.116)
|
5
6
|
|
6
7
|
GEM
|
7
8
|
remote: https://rubygems.org/
|
8
9
|
specs:
|
9
10
|
ast (2.4.2)
|
10
11
|
coderay (1.1.3)
|
12
|
+
datet (0.0.25)
|
11
13
|
diff-lcs (1.5.0)
|
12
|
-
|
14
|
+
http2 (0.0.36)
|
15
|
+
string-cases (~> 0)
|
16
|
+
json (2.6.3)
|
17
|
+
knjrbfw (0.0.116)
|
18
|
+
datet
|
19
|
+
http2
|
20
|
+
php4r
|
21
|
+
ruby_process
|
22
|
+
tsafe
|
23
|
+
wref (>= 0.0.8)
|
13
24
|
method_source (1.0.0)
|
14
|
-
parallel (1.
|
15
|
-
parser (3.
|
25
|
+
parallel (1.23.0)
|
26
|
+
parser (3.2.2.0)
|
16
27
|
ast (~> 2.4.1)
|
17
|
-
|
28
|
+
php4r (0.0.4)
|
29
|
+
datet
|
30
|
+
http2
|
31
|
+
string-strtr
|
32
|
+
pry (0.14.2)
|
18
33
|
coderay (~> 1.1)
|
19
34
|
method_source (~> 1.0)
|
20
35
|
rainbow (3.1.1)
|
21
36
|
rake (13.0.6)
|
22
|
-
regexp_parser (2.
|
37
|
+
regexp_parser (2.8.0)
|
23
38
|
rexml (3.2.5)
|
24
|
-
rspec (3.
|
25
|
-
rspec-core (~> 3.
|
26
|
-
rspec-expectations (~> 3.
|
27
|
-
rspec-mocks (~> 3.
|
28
|
-
rspec-core (3.
|
29
|
-
rspec-support (~> 3.
|
30
|
-
rspec-expectations (3.
|
39
|
+
rspec (3.12.0)
|
40
|
+
rspec-core (~> 3.12.0)
|
41
|
+
rspec-expectations (~> 3.12.0)
|
42
|
+
rspec-mocks (~> 3.12.0)
|
43
|
+
rspec-core (3.12.0)
|
44
|
+
rspec-support (~> 3.12.0)
|
45
|
+
rspec-expectations (3.12.0)
|
31
46
|
diff-lcs (>= 1.2.0, < 2.0)
|
32
|
-
rspec-support (~> 3.
|
33
|
-
rspec-mocks (3.
|
47
|
+
rspec-support (~> 3.12.0)
|
48
|
+
rspec-mocks (3.12.0)
|
34
49
|
diff-lcs (>= 1.2.0, < 2.0)
|
35
|
-
rspec-support (~> 3.
|
36
|
-
rspec-support (3.
|
37
|
-
rubocop (1.
|
50
|
+
rspec-support (~> 3.12.0)
|
51
|
+
rspec-support (3.12.0)
|
52
|
+
rubocop (1.50.2)
|
38
53
|
json (~> 2.3)
|
39
54
|
parallel (~> 1.10)
|
40
|
-
parser (>= 3.
|
55
|
+
parser (>= 3.2.0.0)
|
41
56
|
rainbow (>= 2.2.2, < 4.0)
|
42
57
|
regexp_parser (>= 1.8, < 3.0)
|
43
58
|
rexml (>= 3.2.5, < 4.0)
|
44
|
-
rubocop-ast (>= 1.
|
59
|
+
rubocop-ast (>= 1.28.0, < 2.0)
|
45
60
|
ruby-progressbar (~> 1.7)
|
46
|
-
unicode-display_width (>=
|
47
|
-
rubocop-ast (1.
|
48
|
-
parser (>= 3.
|
49
|
-
rubocop-
|
61
|
+
unicode-display_width (>= 2.4.0, < 3.0)
|
62
|
+
rubocop-ast (1.28.0)
|
63
|
+
parser (>= 3.2.1.0)
|
64
|
+
rubocop-capybara (2.18.0)
|
65
|
+
rubocop (~> 1.41)
|
66
|
+
rubocop-performance (1.17.1)
|
50
67
|
rubocop (>= 1.7.0, < 2.0)
|
51
68
|
rubocop-ast (>= 0.4.0)
|
52
69
|
rubocop-rake (0.6.0)
|
53
70
|
rubocop (~> 1.0)
|
54
|
-
rubocop-rspec (2.
|
55
|
-
rubocop (~> 1.
|
56
|
-
|
71
|
+
rubocop-rspec (2.20.0)
|
72
|
+
rubocop (~> 1.33)
|
73
|
+
rubocop-capybara (~> 2.17)
|
74
|
+
ruby-progressbar (1.13.0)
|
75
|
+
ruby_process (0.0.13)
|
76
|
+
string-cases
|
77
|
+
tsafe
|
78
|
+
wref
|
57
79
|
string-cases (0.0.4)
|
58
|
-
|
80
|
+
string-strtr (0.0.3)
|
81
|
+
tsafe (0.0.12)
|
82
|
+
unicode-display_width (2.4.2)
|
83
|
+
wref (0.0.8)
|
59
84
|
|
60
85
|
PLATFORMS
|
86
|
+
ruby
|
61
87
|
x86_64-linux
|
62
88
|
|
63
89
|
DEPENDENCIES
|
data/README.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# ProcessBot
|
2
2
|
|
3
|
-
|
3
|
+
Run your app through ProcessBot for automatic restart if crashing, but still support normal deployment through Capistrano.
|
4
4
|
|
5
|
-
|
5
|
+
In the future ProcessBot will also watch memory usage and restart processes if leaking memory automatically and gracefully.
|
6
6
|
|
7
7
|
## Installation
|
8
8
|
|
@@ -12,17 +12,27 @@ Add this line to your application's Gemfile:
|
|
12
12
|
gem 'process_bot'
|
13
13
|
```
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
15
|
+
Add to your `Capfile`:
|
16
|
+
```ruby
|
17
|
+
require "process_bot"
|
18
|
+
install_plugin ProcessBot::Capistrano::Sidekiq
|
19
|
+
install_plugin ProcessBot::Capistrano::Puma
|
20
|
+
```
|
20
21
|
|
21
|
-
|
22
|
+
Add to your `deploy.rb`:
|
23
|
+
```ruby
|
24
|
+
after "deploy:starting", "process_bot:sidekiq:graceful"
|
25
|
+
after "deploy:published", "process_bot:sidekiq:start"
|
26
|
+
after "deploy:failed", "process_bot:sidekiq:start"
|
27
|
+
```
|
22
28
|
|
23
29
|
## Usage
|
24
30
|
|
25
|
-
|
31
|
+
Run commands in the command line like this:
|
32
|
+
|
33
|
+
```bash
|
34
|
+
cap production process_bot:sidekiq:graceful
|
35
|
+
```
|
26
36
|
|
27
37
|
## Development
|
28
38
|
|
data/exe/process_bot
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "pathname"
|
4
|
+
|
5
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
6
|
+
Pathname.new(__FILE__).realpath)
|
7
|
+
|
8
|
+
require "bundler/setup"
|
9
|
+
require "optparse"
|
10
|
+
require "string-cases"
|
11
|
+
require_relative "../lib/process_bot"
|
12
|
+
|
13
|
+
options = ProcessBot::Options.new
|
14
|
+
argv_i = 0
|
15
|
+
|
16
|
+
while argv_i < ARGV.length
|
17
|
+
arg = ARGV.fetch(argv_i)
|
18
|
+
|
19
|
+
if (match = arg.match(/\A--(.+)\Z/))
|
20
|
+
key = match[1].tr("-", "_").to_sym
|
21
|
+
argv_i += 1
|
22
|
+
value = ARGV.fetch(argv_i)
|
23
|
+
|
24
|
+
options.set(key, value)
|
25
|
+
else
|
26
|
+
raise "Unknown option: #{arg}"
|
27
|
+
end
|
28
|
+
|
29
|
+
argv_i += 1
|
30
|
+
end
|
31
|
+
|
32
|
+
ProcessBot::Process
|
33
|
+
.new(options)
|
34
|
+
.execute!
|
@@ -14,62 +14,51 @@ namespace :load do
|
|
14
14
|
set :sidekiq_options_per_process, nil
|
15
15
|
set :sidekiq_user, nil
|
16
16
|
# Rbenv, Chruby, and RVM integration
|
17
|
-
set :rbenv_map_bins, fetch(:rbenv_map_bins).to_a
|
18
|
-
set :rvm_map_bins, fetch(:rvm_map_bins).to_a
|
19
|
-
set :chruby_map_bins, fetch(:chruby_map_bins).to_a
|
17
|
+
set :rbenv_map_bins, fetch(:rbenv_map_bins).to_a + ["sidekiq", "sidekiqctl"]
|
18
|
+
set :rvm_map_bins, fetch(:rvm_map_bins).to_a + ["sidekiq", "sidekiqctl"]
|
19
|
+
set :chruby_map_bins, fetch(:chruby_map_bins).to_a + ["sidekiq", "sidekiqctl"]
|
20
20
|
# Bundler integration
|
21
|
-
set :bundle_bins, fetch(:bundle_bins).to_a
|
21
|
+
set :bundle_bins, fetch(:bundle_bins).to_a + ["sidekiq", "sidekiqctl"]
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
25
|
namespace :process_bot do
|
26
26
|
namespace :sidekiq do
|
27
|
-
desc "
|
28
|
-
task :
|
27
|
+
desc "Stop Sidekiq and ProcessBot gracefully (stop fetching new tasks from Redis and then quit when nothing is running)"
|
28
|
+
task :graceful do
|
29
29
|
on roles fetch(:sidekiq_roles) do |role|
|
30
30
|
git_plugin.switch_user(role) do
|
31
|
-
git_plugin.
|
32
|
-
git_plugin.
|
31
|
+
git_plugin.running_process_bot_processes.each do |process_bot_process|
|
32
|
+
git_plugin.process_bot_command(process_bot_process, :graceful)
|
33
33
|
end
|
34
34
|
end
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
|
-
desc "Stop Sidekiq (graceful shutdown within timeout, put unfinished tasks back to Redis)"
|
38
|
+
desc "Stop Sidekiq and ProcessBot (graceful shutdown within timeout, put unfinished tasks back to Redis)"
|
39
39
|
task :stop do
|
40
40
|
on roles fetch(:sidekiq_roles) do |role|
|
41
41
|
git_plugin.switch_user(role) do
|
42
|
-
git_plugin.
|
43
|
-
git_plugin.
|
42
|
+
git_plugin.running_process_bot_processes.each do |process_bot_data|
|
43
|
+
git_plugin.process_bot_command(process_bot_data, :stop)
|
44
44
|
end
|
45
45
|
end
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
49
|
-
desc "
|
50
|
-
task :stop_after_time do
|
51
|
-
on roles fetch(:sidekiq_roles) do |role|
|
52
|
-
git_plugin.switch_user(role) do
|
53
|
-
git_plugin.running_sidekiq_processes.each do |sidekiq_process|
|
54
|
-
git_plugin.stop_sidekiq_after_time(pid: sidekiq_process.fetch(:pid), signal: "TERM")
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
desc "Start sidekiq"
|
49
|
+
desc "Start Sidekiq and ProcessBot"
|
61
50
|
task :start do
|
62
51
|
on roles fetch(:sidekiq_roles) do |role|
|
63
52
|
git_plugin.switch_user(role) do
|
64
53
|
fetch(:sidekiq_processes).times do |idx|
|
65
|
-
puts "Starting Sidekiq #{idx}"
|
54
|
+
puts "Starting Sidekiq with ProcessBot #{idx}"
|
66
55
|
git_plugin.start_sidekiq(idx)
|
67
56
|
end
|
68
57
|
end
|
69
58
|
end
|
70
59
|
end
|
71
60
|
|
72
|
-
desc "Restart
|
61
|
+
desc "Restart Sidekiq and ProcessBot"
|
73
62
|
task :restart do
|
74
63
|
invoke! "process_bot:sidekiq:stop"
|
75
64
|
invoke! "process_bot:sidekiq:start"
|
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
require "json"
|
2
|
+
|
3
|
+
module ProcessBot::Capistrano::SidekiqHelpers # rubocop:disable Metrics/ModuleLength
|
2
4
|
def sidekiq_require
|
3
5
|
"--require #{fetch(:sidekiq_require)}" if fetch(:sidekiq_require)
|
4
6
|
end
|
@@ -30,41 +32,44 @@ module ProcessBot::Capistrano::SidekiqHelpers
|
|
30
32
|
end
|
31
33
|
end
|
32
34
|
|
33
|
-
|
34
|
-
|
35
|
-
raise "Invalid PID: #{pid}" unless pid.to_s.match?(/\A\d+\Z/)
|
36
|
-
raise "Invalid signal: #{signal}" unless VALID_SIGNALS.include?(signal)
|
37
|
-
|
38
|
-
backend.execute "kill -#{signal} #{pid}"
|
39
|
-
end
|
35
|
+
def process_bot_command(process_bot_data, command)
|
36
|
+
raise "No port in process bot data? #{process_bot_data}" unless process_bot_data["port"]
|
40
37
|
|
41
|
-
|
42
|
-
|
43
|
-
|
38
|
+
backend_command = "cd #{release_path} && " \
|
39
|
+
"#{SSHKit.config.command_map.prefix[:bundle].join(" ")} bundle exec process_bot " \
|
40
|
+
"--command #{command} " \
|
41
|
+
"--port #{process_bot_data.fetch("port")}"
|
44
42
|
|
45
|
-
|
46
|
-
|
43
|
+
if command == :graceful && !fetch(:process_bot_wait_for_gracefully_stopped).nil?
|
44
|
+
backend_command << " --wait-for-gracefully-stopped #{fetch(:process_bot_wait_for_gracefully_stopped)}"
|
45
|
+
end
|
47
46
|
|
48
|
-
backend.execute
|
47
|
+
backend.execute backend_command
|
49
48
|
end
|
50
49
|
|
51
|
-
def
|
50
|
+
def running_process_bot_processes
|
52
51
|
sidekiq_app_name = fetch(:sidekiq_app_name, fetch(:application))
|
53
52
|
raise "No :sidekiq_app_name was set" unless sidekiq_app_name
|
54
53
|
|
55
54
|
begin
|
56
|
-
processes_output = backend.capture("ps a |
|
55
|
+
processes_output = backend.capture("ps a | grep ProcessBot | grep sidekiq | grep -v '/usr/bin/SCREEN' | grep '#{Regexp.escape(sidekiq_app_name)}'")
|
57
56
|
rescue SSHKit::Command::Failed
|
58
57
|
# Fails when output is empty (when no processes found through grep)
|
59
|
-
puts "No Sidekiq processes found"
|
58
|
+
puts "No ProcessBot Sidekiq processes found"
|
60
59
|
return []
|
61
60
|
end
|
62
61
|
|
62
|
+
parse_process_bot_process_from_ps(processes_output)
|
63
|
+
end
|
64
|
+
|
65
|
+
def parse_process_bot_process_from_ps(processes_output)
|
63
66
|
processes = []
|
64
|
-
processes_output.scan(/^\s*(\d+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(
|
65
|
-
|
67
|
+
processes_output.scan(/^\s*(\d+)\s+(\S+)\s+(\S+)\s+(\S+)\s+ProcessBot (\{([^\n]+?)\})$/).each do |process_output|
|
68
|
+
process_bot_data = JSON.parse(process_output[4])
|
69
|
+
process_bot_pid = process_output[0]
|
70
|
+
process_bot_data["process_bot_pid"] = process_bot_pid
|
66
71
|
|
67
|
-
processes <<
|
72
|
+
processes << process_bot_data
|
68
73
|
end
|
69
74
|
|
70
75
|
processes
|
@@ -86,7 +91,7 @@ module ProcessBot::Capistrano::SidekiqHelpers
|
|
86
91
|
backend.capture(:echo, SSHKit.config.command_map[:bundle]).strip
|
87
92
|
end
|
88
93
|
|
89
|
-
def start_sidekiq(idx = 0) # rubocop:disable Metrics/AbcSize
|
94
|
+
def start_sidekiq(idx = 0) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
90
95
|
releases = backend.capture(:ls, "-x", releases_path).split
|
91
96
|
releases << release_timestamp.to_s if release_timestamp
|
92
97
|
releases.uniq
|
@@ -95,13 +100,19 @@ module ProcessBot::Capistrano::SidekiqHelpers
|
|
95
100
|
raise "Invalid release timestamp: #{release_timestamp}" unless latest_release_version
|
96
101
|
|
97
102
|
args = [
|
103
|
+
"--command", "start",
|
98
104
|
"--id", "sidekiq-#{latest_release_version}-#{idx}",
|
105
|
+
"--application", fetch(:sidekiq_app_name, fetch(:application)),
|
99
106
|
"--handler", "sidekiq",
|
100
107
|
"--bundle-prefix", SSHKit.config.command_map.prefix[:bundle].join(" "),
|
101
108
|
"--sidekiq-environment", fetch(:sidekiq_env),
|
102
|
-
"--port",
|
109
|
+
"--port", idx + 7050,
|
110
|
+
"--release-path", release_path
|
103
111
|
]
|
104
|
-
|
112
|
+
|
113
|
+
# Use screen for logging everything which is why this is disabled
|
114
|
+
# args += ["--log-file-path", fetch(:sidekiq_log)] if fetch(:sidekiq_log)
|
115
|
+
|
105
116
|
args += ["--sidekiq-require", fetch(:sidekiq_require)] if fetch(:sidekiq_require)
|
106
117
|
args += ["--sidekiq-tag", fetch(:sidekiq_tag)] if fetch(:sidekiq_tag)
|
107
118
|
args += ["--sidekiq-queues", Array(fetch(:sidekiq_queue)).join(",")] if fetch(:sidekiq_queue)
|
@@ -112,13 +123,18 @@ module ProcessBot::Capistrano::SidekiqHelpers
|
|
112
123
|
end
|
113
124
|
args += fetch(:sidekiq_options) if fetch(:sidekiq_options)
|
114
125
|
|
115
|
-
screen_args = ["-dmS sidekiq
|
116
|
-
|
126
|
+
screen_args = ["-dmS process-bot--sidekiq--#{idx}-#{latest_release_version}"]
|
127
|
+
|
128
|
+
if (process_bot_sidekiq_log = fetch(:process_bot_sidekig_log))
|
129
|
+
screen_args << "-L -Logfile #{process_bot_sidekiq_log}"
|
130
|
+
elsif fetch(:sidekiq_log)
|
131
|
+
screen_args << "-L -Logfile #{fetch(:sidekiq_log)}"
|
132
|
+
end
|
117
133
|
|
118
134
|
process_bot_args = args.compact.map { |arg| "\"#{arg}\"" }
|
119
135
|
|
120
136
|
command = "/usr/bin/screen #{screen_args.join(" ")} " \
|
121
|
-
"bash -c 'cd #{release_path} && #{SSHKit.config.command_map.prefix[:bundle].join(" ")} bundle exec process_bot #{process_bot_args.join(" ")}'"
|
137
|
+
"bash -c 'cd #{release_path} && exec #{SSHKit.config.command_map.prefix[:bundle].join(" ")} bundle exec process_bot #{process_bot_args.join(" ")}'"
|
122
138
|
|
123
139
|
puts "WARNING: A known bug prevents Sidekiq from starting when pty is set (which it is)" if fetch(:pty)
|
124
140
|
puts "ProcessBot Sidekiq command: #{command}"
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "socket"
|
2
|
+
|
3
|
+
class ProcessBot::ClientSocket
|
4
|
+
attr_reader :options
|
5
|
+
|
6
|
+
def initialize(options:)
|
7
|
+
@options = options
|
8
|
+
end
|
9
|
+
|
10
|
+
def client
|
11
|
+
@client ||= TCPSocket.new("localhost", options.fetch(:port).to_i)
|
12
|
+
end
|
13
|
+
|
14
|
+
def close
|
15
|
+
client.close
|
16
|
+
end
|
17
|
+
|
18
|
+
def send_command(data)
|
19
|
+
client.puts(JSON.generate(data))
|
20
|
+
response_raw = client.gets
|
21
|
+
|
22
|
+
# Happens if process is interrupted
|
23
|
+
return :nil if response_raw.nil?
|
24
|
+
|
25
|
+
response = JSON.parse(response_raw)
|
26
|
+
|
27
|
+
return :success if response.fetch("type") == "success"
|
28
|
+
|
29
|
+
raise "Command raised an error: #{response.fetch("message")}" if response.fetch("type") == "error"
|
30
|
+
end
|
31
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require "socket"
|
2
|
+
|
1
3
|
class ProcessBot::ControlSocket
|
2
4
|
attr_reader :options, :process, :server
|
3
5
|
|
@@ -11,9 +13,16 @@ class ProcessBot::ControlSocket
|
|
11
13
|
end
|
12
14
|
|
13
15
|
def start
|
14
|
-
|
16
|
+
@server = TCPServer.new("localhost", port)
|
17
|
+
run_client_loop
|
18
|
+
|
19
|
+
puts "TCPServer started"
|
20
|
+
|
21
|
+
options.events.call(:on_socket_opened, port: port)
|
22
|
+
end
|
15
23
|
|
16
|
-
|
24
|
+
def stop
|
25
|
+
server.close
|
17
26
|
end
|
18
27
|
|
19
28
|
def run_client_loop
|
@@ -26,14 +35,26 @@ class ProcessBot::ControlSocket
|
|
26
35
|
end
|
27
36
|
end
|
28
37
|
|
29
|
-
def handle_client(client)
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
38
|
+
def handle_client(client) # rubocop:disable Metrics/AbcSize
|
39
|
+
loop do
|
40
|
+
data = client.gets
|
41
|
+
break if data.nil? # Client disconnected
|
42
|
+
|
43
|
+
command = JSON.parse(data)
|
44
|
+
command_type = command.fetch("command")
|
45
|
+
|
46
|
+
if command_type == "graceful" || command_type == "stop"
|
47
|
+
begin
|
48
|
+
process.__send__(command_type)
|
49
|
+
client.puts(JSON.generate(type: "success"))
|
50
|
+
rescue => e # rubocop:disable Style/RescueStandardError
|
51
|
+
client.puts(JSON.generate(type: "error", message: e.message))
|
52
|
+
|
53
|
+
raise e
|
54
|
+
end
|
55
|
+
else
|
56
|
+
client.puts(JSON.generate(type: "error", message: "Unknown command: #{command_type}"))
|
57
|
+
end
|
37
58
|
end
|
38
59
|
end
|
39
60
|
end
|
data/lib/process_bot/logger.rb
CHANGED
@@ -1,22 +1,34 @@
|
|
1
1
|
class ProcessBot::Logger
|
2
|
-
attr_reader :
|
2
|
+
attr_reader :options
|
3
3
|
|
4
4
|
def initialize(options:)
|
5
5
|
@options = options
|
6
|
-
|
7
|
-
open_file
|
8
6
|
end
|
9
7
|
|
10
|
-
def log(output)
|
11
|
-
|
12
|
-
|
8
|
+
def log(output, type: :stdout)
|
9
|
+
if type == :stdout
|
10
|
+
$stdout.print output
|
11
|
+
elsif type == :stderr
|
12
|
+
$stderr.print output
|
13
|
+
else
|
14
|
+
raise "Unknown type: #{type}"
|
15
|
+
end
|
16
|
+
|
17
|
+
return unless log_to_file?
|
18
|
+
|
19
|
+
fp_log.write(output)
|
20
|
+
fp_log.flush
|
13
21
|
end
|
14
22
|
|
15
23
|
def log_file_path
|
16
24
|
options.fetch(:log_file_path)
|
17
25
|
end
|
18
26
|
|
19
|
-
def
|
20
|
-
|
27
|
+
def log_to_file?
|
28
|
+
options.present?(:log_file_path)
|
29
|
+
end
|
30
|
+
|
31
|
+
def fp_log
|
32
|
+
@fp_log ||= File.open(log_file_path, "a") if log_to_file?
|
21
33
|
end
|
22
34
|
end
|
data/lib/process_bot/options.rb
CHANGED
@@ -5,8 +5,59 @@ class ProcessBot::Options
|
|
5
5
|
@options = options
|
6
6
|
end
|
7
7
|
|
8
|
-
def
|
9
|
-
options
|
8
|
+
def [](key)
|
9
|
+
options[key]
|
10
|
+
end
|
11
|
+
|
12
|
+
def events
|
13
|
+
@events ||= begin
|
14
|
+
require "knjrbfw"
|
15
|
+
|
16
|
+
event_handler = ::Knj::Event_handler.new
|
17
|
+
event_handler.add_event(name: :on_process_started)
|
18
|
+
event_handler.add_event(name: :on_socket_opened)
|
19
|
+
event_handler
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def fetch(...)
|
24
|
+
options.fetch(...)
|
25
|
+
end
|
26
|
+
|
27
|
+
def application_basename
|
28
|
+
@application_basename ||= begin
|
29
|
+
app_path_parts = release_path.split("/")
|
30
|
+
|
31
|
+
if release_path.include?("/releases/")
|
32
|
+
app_path_parts.pop(2)
|
33
|
+
elsif release_path.end_with?("/current")
|
34
|
+
app_path_parts.pop
|
35
|
+
end
|
36
|
+
|
37
|
+
app_path_parts.last
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def possible_process_titles
|
42
|
+
possible_names = []
|
43
|
+
|
44
|
+
# Sidekiq name can by current Rails root base name
|
45
|
+
possible_names << application_basename
|
46
|
+
|
47
|
+
# Sidekiq name can be set tag name (but we wrongly read application for some reason?)
|
48
|
+
possible_names << options.fetch(:application)
|
49
|
+
|
50
|
+
possible_names
|
51
|
+
end
|
52
|
+
|
53
|
+
def possible_process_titles_joined_regex
|
54
|
+
possible_process_titles_joined_regex = ""
|
55
|
+
possible_process_titles.each_with_index do |possible_name, index|
|
56
|
+
possible_process_titles_joined_regex << "|" if index >= 1
|
57
|
+
possible_process_titles_joined_regex << Regexp.escape(possible_name)
|
58
|
+
end
|
59
|
+
|
60
|
+
possible_process_titles_joined_regex
|
10
61
|
end
|
11
62
|
|
12
63
|
def present?(key)
|
@@ -15,6 +66,10 @@ class ProcessBot::Options
|
|
15
66
|
false
|
16
67
|
end
|
17
68
|
|
69
|
+
def release_path
|
70
|
+
@release_path ||= fetch(:release_path)
|
71
|
+
end
|
72
|
+
|
18
73
|
def set(key, value)
|
19
74
|
options[key] = value
|
20
75
|
end
|
@@ -3,8 +3,6 @@ class ProcessBot::Process::Handlers::Sidekiq
|
|
3
3
|
|
4
4
|
def initialize(options)
|
5
5
|
@options = options
|
6
|
-
|
7
|
-
set_defaults
|
8
6
|
end
|
9
7
|
|
10
8
|
def fetch(*args, **opts)
|
@@ -21,35 +19,27 @@ class ProcessBot::Process::Handlers::Sidekiq
|
|
21
19
|
options.set(*args, **opts)
|
22
20
|
end
|
23
21
|
|
24
|
-
def
|
25
|
-
set :sidekiq_default_hooks, true
|
26
|
-
set :sidekiq_pid, -> { File.join(shared_path, "tmp", "pids", "sidekiq.pid") }
|
27
|
-
set :sidekiq_timeout, 10
|
28
|
-
set :sidekiq_roles, fetch(:sidekiq_role, :app)
|
29
|
-
set :sidekiq_processes, 1
|
30
|
-
set :sidekiq_options_per_process, nil
|
31
|
-
end
|
32
|
-
|
33
|
-
def command # rubocop:disable Metrics/AbcSize
|
22
|
+
def start_command # rubocop:disable Metrics/AbcSize
|
34
23
|
args = []
|
35
24
|
|
36
25
|
options.options.each do |key, value|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
else
|
45
|
-
args.push "--#{sidekiq_key} #{value}"
|
26
|
+
next unless (match = key.to_s.match(/\Asidekiq_(.+)\Z/))
|
27
|
+
|
28
|
+
sidekiq_key = match[1]
|
29
|
+
|
30
|
+
if sidekiq_key == "queue"
|
31
|
+
value.split(",").each do |queue|
|
32
|
+
args.push "--queue #{queue}"
|
46
33
|
end
|
34
|
+
else
|
35
|
+
args.push "--#{sidekiq_key} #{value}"
|
47
36
|
end
|
48
37
|
end
|
49
38
|
|
50
|
-
command = ""
|
39
|
+
command = "bash -c 'cd #{options.fetch(:release_path)} && exec "
|
51
40
|
command << "#{options.fetch(:bundle_prefix)} " if options.present?(:bundle_prefix)
|
52
41
|
command << "bundle exec sidekiq #{args.compact.join(' ')}"
|
42
|
+
command << "'"
|
53
43
|
command
|
54
44
|
end
|
55
45
|
end
|
@@ -1,5 +1,7 @@
|
|
1
|
+
require "knjrbfw"
|
2
|
+
|
1
3
|
class ProcessBot::Process::Runner
|
2
|
-
attr_reader :command, :exit_status, :logger, :monitor, :options, :stop_time
|
4
|
+
attr_reader :command, :exit_status, :logger, :monitor, :options, :pid, :stop_time, :subprocess_pid
|
3
5
|
|
4
6
|
def initialize(command:, logger:, options:)
|
5
7
|
@command = command
|
@@ -9,8 +11,12 @@ class ProcessBot::Process::Runner
|
|
9
11
|
@output = []
|
10
12
|
end
|
11
13
|
|
12
|
-
def output(output:, type:)
|
13
|
-
logger.log(output)
|
14
|
+
def output(output:, type:)
|
15
|
+
logger.log(output, type: type)
|
16
|
+
end
|
17
|
+
|
18
|
+
def running?
|
19
|
+
!stop_time
|
14
20
|
end
|
15
21
|
|
16
22
|
def run # rubocop:disable Metrics/AbcSize
|
@@ -20,8 +26,8 @@ class ProcessBot::Process::Runner
|
|
20
26
|
require "pty"
|
21
27
|
|
22
28
|
PTY.spawn(command, err: stderr_writer.fileno) do |stdout, _stdin, pid|
|
23
|
-
@
|
24
|
-
logger.log "Command running with PID #{pid}: #{command}"
|
29
|
+
@subprocess_pid = pid
|
30
|
+
logger.log "Command running with PID #{pid}: #{command}\n"
|
25
31
|
|
26
32
|
stdout_reader_thread = Thread.new do
|
27
33
|
stdout.each_char do |chunk|
|
@@ -32,7 +38,7 @@ class ProcessBot::Process::Runner
|
|
32
38
|
rescue Errno::EIO
|
33
39
|
# Process done
|
34
40
|
ensure
|
35
|
-
status = Process::Status.wait(
|
41
|
+
status = Process::Status.wait(subprocess_pid, 0)
|
36
42
|
|
37
43
|
@exit_status = status.exitstatus
|
38
44
|
stderr_writer.close
|
@@ -46,10 +52,56 @@ class ProcessBot::Process::Runner
|
|
46
52
|
end
|
47
53
|
end
|
48
54
|
|
55
|
+
find_sidekiq_pid
|
56
|
+
|
49
57
|
stdout_reader_thread.join
|
50
58
|
stderr_reader_thread.join
|
51
59
|
|
52
60
|
@stop_time = Time.new
|
53
61
|
end
|
54
62
|
end
|
63
|
+
|
64
|
+
def subprocess_pgid
|
65
|
+
@subprocess_pgid ||= Process.getpgid(subprocess_pid)
|
66
|
+
end
|
67
|
+
|
68
|
+
def sidekiq_app_name
|
69
|
+
options.fetch(:application)
|
70
|
+
end
|
71
|
+
|
72
|
+
def find_sidekiq_pid # rubocop:disable Metrics/AbcSize
|
73
|
+
Thread.new do
|
74
|
+
while running? && !pid
|
75
|
+
Knj::Unix_proc.list("grep" => "sidekiq") do |process|
|
76
|
+
cmd = process.data.fetch("cmd")
|
77
|
+
|
78
|
+
if /sidekiq ([0-9]+\.[0-9]+\.[0-9]+) (#{options.possible_process_titles_joined_regex})/.match?(cmd)
|
79
|
+
sidekiq_pid = process.data.fetch("pid").to_i
|
80
|
+
|
81
|
+
begin
|
82
|
+
sidekiq_pgid = Process.getpgid(sidekiq_pid)
|
83
|
+
rescue Errno::ESRCH
|
84
|
+
# Process no longer running
|
85
|
+
end
|
86
|
+
|
87
|
+
if subprocess_pgid == sidekiq_pgid
|
88
|
+
puts "FOUND PID: #{sidekiq_pid}"
|
89
|
+
|
90
|
+
@pid = sidekiq_pid
|
91
|
+
options.events.call(:on_process_started, pid: pid)
|
92
|
+
|
93
|
+
break
|
94
|
+
else
|
95
|
+
puts "PGID didn't match - Sidekiq: #{sidekiq_pgid} Own: #{subprocess_pgid}"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
unless pid
|
101
|
+
puts "Waiting 1 second before trying to find Sidekiq PID again"
|
102
|
+
sleep 1
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
55
107
|
end
|
data/lib/process_bot/process.rb
CHANGED
@@ -1,35 +1,68 @@
|
|
1
|
+
require "json"
|
2
|
+
|
1
3
|
class ProcessBot::Process
|
2
4
|
autoload :Handlers, "#{__dir__}/process/handlers"
|
3
5
|
autoload :Runner, "#{__dir__}/process/runner"
|
4
6
|
|
5
|
-
attr_reader :options, :stopped
|
7
|
+
attr_reader :current_pid, :current_process_title, :options, :port, :stopped
|
6
8
|
|
7
9
|
def initialize(options)
|
8
10
|
@options = options
|
9
11
|
@stopped = false
|
10
|
-
end
|
11
12
|
|
12
|
-
|
13
|
-
|
13
|
+
options.events.connect(:on_process_started, &method(:on_process_started)) # rubocop:disable Performance/MethodObjectAsBlock
|
14
|
+
options.events.connect(:on_socket_opened, &method(:on_socket_opened)) # rubocop:disable Performance/MethodObjectAsBlock
|
15
|
+
|
16
|
+
logger.log("Options: #{options.options}")
|
14
17
|
end
|
15
18
|
|
16
|
-
def
|
17
|
-
|
18
|
-
|
19
|
+
def execute!
|
20
|
+
command = options.fetch(:command)
|
21
|
+
|
22
|
+
if command == "start"
|
23
|
+
start
|
24
|
+
elsif command == "graceful" || command == "stop"
|
25
|
+
client.send_command(command: command)
|
26
|
+
else
|
27
|
+
raise "Unknown command: #{command}"
|
28
|
+
end
|
19
29
|
end
|
20
30
|
|
21
|
-
def
|
22
|
-
@
|
31
|
+
def client
|
32
|
+
@client ||= ProcessBot::ClientSocket.new(options: options)
|
23
33
|
end
|
24
34
|
|
25
35
|
def handler_class
|
26
36
|
@handler_class ||= begin
|
27
|
-
require_relative "process/handlers/#{
|
28
|
-
ProcessBot::Process::Handlers.const_get(StringCases.snake_to_camel(
|
37
|
+
require_relative "process/handlers/#{handler_name}"
|
38
|
+
ProcessBot::Process::Handlers.const_get(StringCases.snake_to_camel(handler_name))
|
29
39
|
end
|
30
40
|
end
|
31
41
|
|
32
|
-
def
|
42
|
+
def handler_name
|
43
|
+
@handler_name ||= options.fetch(:handler)
|
44
|
+
end
|
45
|
+
|
46
|
+
def logger
|
47
|
+
@logger ||= ProcessBot::Logger.new(options: options)
|
48
|
+
end
|
49
|
+
|
50
|
+
def on_process_started(_event_name, pid:)
|
51
|
+
@current_pid = pid
|
52
|
+
update_process_title
|
53
|
+
end
|
54
|
+
|
55
|
+
def on_socket_opened(_event_name, port:)
|
56
|
+
@port = port
|
57
|
+
update_process_title
|
58
|
+
end
|
59
|
+
|
60
|
+
def start_control_socket
|
61
|
+
@control_socket = ProcessBot::ControlSocket.new(options: options, process: self)
|
62
|
+
@control_socket.start
|
63
|
+
end
|
64
|
+
|
65
|
+
def start
|
33
66
|
start_control_socket
|
34
67
|
|
35
68
|
loop do
|
@@ -44,9 +77,77 @@ class ProcessBot::Process
|
|
44
77
|
end
|
45
78
|
end
|
46
79
|
|
80
|
+
def graceful
|
81
|
+
@stopped = true
|
82
|
+
|
83
|
+
unless current_pid
|
84
|
+
warn "#{handler_name} not running with a PID"
|
85
|
+
return
|
86
|
+
end
|
87
|
+
|
88
|
+
Process.kill("TSTP", current_pid)
|
89
|
+
|
90
|
+
if options[:wait_for_gracefully_stopped] == "false"
|
91
|
+
Thread.new { wait_for_no_jobs_and_stop_sidekiq }
|
92
|
+
else
|
93
|
+
wait_for_no_jobs_and_stop_sidekiq
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def stop
|
98
|
+
@stopped = true
|
99
|
+
|
100
|
+
unless current_pid
|
101
|
+
warn "#{handler_name} not running with a PID"
|
102
|
+
return
|
103
|
+
end
|
104
|
+
|
105
|
+
Process.kill("TERM", current_pid)
|
106
|
+
end
|
107
|
+
|
47
108
|
def run
|
48
109
|
handler_instance = handler_class.new(options)
|
49
|
-
runner = ProcessBot::Process::Runner.new(command: handler_instance.
|
110
|
+
runner = ProcessBot::Process::Runner.new(command: handler_instance.start_command, logger: logger, options: options)
|
50
111
|
runner.run
|
51
112
|
end
|
113
|
+
|
114
|
+
def update_process_title
|
115
|
+
process_args = {application: options[:application], handler: handler_name, id: options[:id], pid: current_pid, port: port}
|
116
|
+
@current_process_title = "ProcessBot #{JSON.generate(process_args)}"
|
117
|
+
Process.setproctitle(current_process_title)
|
118
|
+
end
|
119
|
+
|
120
|
+
def wait_for_no_jobs # rubocop:disable Metrics/AbcSize
|
121
|
+
loop do
|
122
|
+
found_process = false
|
123
|
+
|
124
|
+
Knj::Unix_proc.list("grep" => current_pid) do |process|
|
125
|
+
process_command = process.data.fetch("cmd")
|
126
|
+
process_pid = process.data.fetch("pid").to_i
|
127
|
+
next unless process_pid == current_pid
|
128
|
+
|
129
|
+
found_process = true
|
130
|
+
sidekiq_regex = /\Asidekiq (\d+).(\d+).(\d+) (#{options.possible_process_titles_joined_regex}) \[(\d+) of (\d+)(\]|) (.+?)(\]|)\Z/
|
131
|
+
match = process_command.match(sidekiq_regex)
|
132
|
+
raise "Couldnt match Sidekiq command: #{process_command} with Sidekiq regex: #{sidekiq_regex}" unless match
|
133
|
+
|
134
|
+
running_jobs = match[5].to_i
|
135
|
+
|
136
|
+
puts "running_jobs: #{running_jobs}"
|
137
|
+
|
138
|
+
return if running_jobs.zero? # rubocop:disable Lint/NonLocalExitFromIterator
|
139
|
+
end
|
140
|
+
|
141
|
+
raise "Couldn't find running process with PID #{current_pid}" unless found_process
|
142
|
+
|
143
|
+
sleep 1
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def wait_for_no_jobs_and_stop_sidekiq
|
148
|
+
puts "Wait for no jobs and Stop sidekiq"
|
149
|
+
|
150
|
+
wait_for_no_jobs
|
151
|
+
stop
|
152
|
+
end
|
52
153
|
end
|
data/lib/process_bot/version.rb
CHANGED
data/lib/process_bot.rb
CHANGED
@@ -4,6 +4,7 @@ module ProcessBot
|
|
4
4
|
class Error < StandardError; end
|
5
5
|
|
6
6
|
autoload :Capistrano, "#{__dir__}/process_bot/capistrano"
|
7
|
+
autoload :ClientSocket, "#{__dir__}/process_bot/client_socket"
|
7
8
|
autoload :ControlSocket, "#{__dir__}/process_bot/control_socket"
|
8
9
|
autoload :Logger, "#{__dir__}/process_bot/logger"
|
9
10
|
autoload :Options, "#{__dir__}/process_bot/options"
|
data/peak_flow.yml
CHANGED
@@ -1,4 +1,20 @@
|
|
1
1
|
rvm: true
|
2
|
-
|
3
|
-
|
4
|
-
|
2
|
+
builds:
|
3
|
+
build_1:
|
4
|
+
environment:
|
5
|
+
RUBY_VERSION: 2.7.8
|
6
|
+
name: Ruby 2.7.8
|
7
|
+
script:
|
8
|
+
- bundle exec rspec
|
9
|
+
build_2:
|
10
|
+
environment:
|
11
|
+
RUBY_VERSION: 3.2.2
|
12
|
+
name: Ruby 3.2.2
|
13
|
+
script:
|
14
|
+
- bundle exec rspec
|
15
|
+
build_3:
|
16
|
+
environment:
|
17
|
+
RUBY_VERSION: 2.7.8
|
18
|
+
name: Rubocop
|
19
|
+
script:
|
20
|
+
- bundle exec rubocop
|
data/process_bot.gemspec
CHANGED
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
|
|
12
12
|
spec.description = "Run and control processes."
|
13
13
|
spec.homepage = "https://github.com/kaspernj/process_bot"
|
14
14
|
spec.license = "MIT"
|
15
|
-
spec.required_ruby_version = ">= 2.
|
15
|
+
spec.required_ruby_version = ">= 2.7.0"
|
16
16
|
|
17
17
|
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
18
18
|
|
@@ -24,22 +24,14 @@ Gem::Specification.new do |spec|
|
|
24
24
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
25
25
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
26
26
|
`git ls-files -z`.split("\x0").reject do |f|
|
27
|
-
(f == __FILE__) || f.match(%r{\A(?:(?:bin|
|
27
|
+
(f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
28
28
|
end
|
29
29
|
end
|
30
30
|
spec.bindir = "exe"
|
31
31
|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
32
32
|
spec.require_paths = ["lib"]
|
33
33
|
|
34
|
-
|
35
|
-
# spec.add_dependency "example-gem", "~> 1.0"
|
34
|
+
spec.add_dependency "knjrbfw", ">= 0.0.116"
|
36
35
|
|
37
|
-
spec.add_development_dependency "rubocop"
|
38
|
-
spec.add_development_dependency "rubocop-performance"
|
39
|
-
spec.add_development_dependency "rubocop-rake"
|
40
|
-
spec.add_development_dependency "rubocop-rspec"
|
41
|
-
|
42
|
-
# For more information and examples about making a new gem, check out our
|
43
|
-
# guide at: https://bundler.io/guides/creating_gem.html
|
44
36
|
spec.metadata["rubygems_mfa_required"] = "true"
|
45
37
|
end
|
metadata
CHANGED
@@ -1,86 +1,47 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: process_bot
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- kaspernj
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-07-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: knjrbfw
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
20
|
-
type: :
|
19
|
+
version: 0.0.116
|
20
|
+
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: rubocop-performance
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - ">="
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '0'
|
34
|
-
type: :development
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - ">="
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '0'
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: rubocop-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: rubocop-rspec
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - ">="
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '0'
|
62
|
-
type: :development
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - ">="
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '0'
|
26
|
+
version: 0.0.116
|
69
27
|
description: Run and control processes.
|
70
28
|
email:
|
71
29
|
- k@spernj.org
|
72
|
-
executables:
|
30
|
+
executables:
|
31
|
+
- process_bot
|
73
32
|
extensions: []
|
74
33
|
extra_rdoc_files: []
|
75
34
|
files:
|
76
35
|
- ".rspec"
|
77
36
|
- ".rubocop.yml"
|
37
|
+
- ".ruby-version"
|
78
38
|
- CHANGELOG.md
|
79
39
|
- Gemfile
|
80
40
|
- Gemfile.lock
|
81
41
|
- LICENSE.txt
|
82
42
|
- README.md
|
83
43
|
- Rakefile
|
44
|
+
- exe/process_bot
|
84
45
|
- lib/process_bot.rb
|
85
46
|
- lib/process_bot/capistrano.rb
|
86
47
|
- lib/process_bot/capistrano/puma.rake
|
@@ -89,6 +50,7 @@ files:
|
|
89
50
|
- lib/process_bot/capistrano/sidekiq.rake
|
90
51
|
- lib/process_bot/capistrano/sidekiq.rb
|
91
52
|
- lib/process_bot/capistrano/sidekiq_helpers.rb
|
53
|
+
- lib/process_bot/client_socket.rb
|
92
54
|
- lib/process_bot/control_socket.rb
|
93
55
|
- lib/process_bot/logger.rb
|
94
56
|
- lib/process_bot/options.rb
|
@@ -117,14 +79,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
117
79
|
requirements:
|
118
80
|
- - ">="
|
119
81
|
- !ruby/object:Gem::Version
|
120
|
-
version: 2.
|
82
|
+
version: 2.7.0
|
121
83
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
122
84
|
requirements:
|
123
85
|
- - ">="
|
124
86
|
- !ruby/object:Gem::Version
|
125
87
|
version: '0'
|
126
88
|
requirements: []
|
127
|
-
rubygems_version: 3.
|
89
|
+
rubygems_version: 3.1.6
|
128
90
|
signing_key:
|
129
91
|
specification_version: 4
|
130
92
|
summary: Run and control processes.
|