process_bot 0.1.0

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: f1bb2b0296a51e0b52b952f49f6724d95d7478181539b75fac529ff3ab3d82de
4
+ data.tar.gz: d7ec454928d4e72f40e9a5d5cf05e2fff930cf73de754c47fb344209173294aa
5
+ SHA512:
6
+ metadata.gz: c0183b4d9a6f4023393467dc1fb794b2c1fc1c30d481a24b1cbb9728f714be5b2bd1f5e2620c71e60394e59aa79a5fef65f42cc152553007939dda0244635507
7
+ data.tar.gz: 8ee7928cf14abedbda792e50653fad619c9ac89196dc7effb0869fcda37141dd637fd63085075e7068cec50d1d903ff69b6216fb446939405654683b000bf4bc
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,13 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.6
3
+
4
+ Style/StringLiterals:
5
+ Enabled: true
6
+ EnforcedStyle: double_quotes
7
+
8
+ Style/StringLiteralsInInterpolation:
9
+ Enabled: true
10
+ EnforcedStyle: double_quotes
11
+
12
+ Layout/LineLength:
13
+ Max: 120
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2022-04-03
4
+
5
+ - Initial release
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in process_bot.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 13.0"
9
+
10
+ gem "rspec", "~> 3.0"
11
+
12
+ gem "rubocop", "~> 1.21"
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2022 kaspernj
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # ProcessBot
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/process_bot`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'process_bot'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle install
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install process_bot
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
29
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
+
31
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/kaspernj/process_bot.
36
+
37
+ ## License
38
+
39
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
@@ -0,0 +1,90 @@
1
+ git_plugin = self
2
+
3
+ namespace :process_bot do
4
+ namespace :puma do
5
+ desc "Start Puma through ProcessBot"
6
+ task :start do
7
+ on roles(fetch(:puma_role)) do |role|
8
+ git_plugin.puma_switch_user(role) do
9
+ if test "[ -f #{fetch(:puma_pid)} ]" and test :kill, "-0 $( cat #{fetch(:puma_pid)} )"
10
+ info "Puma is already running"
11
+ else
12
+ within current_path do
13
+ with rack_env: fetch(:puma_env) do
14
+ releases = capture(:ls, "-x", releases_path).split
15
+ releases << release_timestamp.to_s if release_timestamp
16
+ releases.uniq
17
+
18
+ latest_release_version = releases.last
19
+ raise "Invalid release timestamp: #{release_timestamp}" unless latest_release_version
20
+
21
+ puma_args = [
22
+ "-C #{fetch(:puma_conf)}",
23
+ "--control-url tcp://127.0.0.1:9293",
24
+ "--control-token foobar"
25
+ ]
26
+
27
+ command = "/usr/bin/screen -dmS puma-#{latest_release_version} bash -c 'cd #{release_path} && #{SSHKit.config.command_map.prefix[:puma].join(" ")} puma #{puma_args.join(" ")}'"
28
+ execute command
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ %w[halt stop status].map do |command|
37
+ desc "#{command} puma"
38
+ task command do
39
+ on roles (fetch(:puma_role)) do |role|
40
+ within current_path do
41
+ git_plugin.puma_switch_user(role) do
42
+ with rack_env: fetch(:puma_env) do
43
+ if test "[ -f #{fetch(:puma_pid)} ]"
44
+ if git_plugin.stop_puma
45
+ git_plugin.run_puma_command(command)
46
+ else
47
+ # delete invalid pid file , process is not running.
48
+ execute :rm, fetch(:puma_pid)
49
+ end
50
+ else
51
+ #pid file not found, so puma is probably not running or it using another pidfile
52
+ warn "Puma not running"
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ %w[phased-restart restart].map do |command|
62
+ desc "#{command} puma"
63
+ task command do
64
+ on roles (fetch(:puma_role)) do |role|
65
+ within current_path do
66
+ git_plugin.puma_switch_user(role) do
67
+ with rack_env: fetch(:puma_env) do
68
+ if git_plugin.puma_running?
69
+ # NOTE pid exist but state file is nonsense, so ignore that case
70
+ git_plugin.run_puma_command(command)
71
+ else
72
+ # Puma is not running or state file is not present : Run it
73
+ invoke "process_bot:puma:start"
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ task :smart_restart do
83
+ if !fetch(:puma_preload_app) && fetch(:puma_workers, 0).to_i > 1
84
+ invoke "process_bot:puma:phased-restart"
85
+ else
86
+ invoke "process_bot:puma:restart"
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,23 @@
1
+ module ProcessBot::Capistrano::Puma < Capistrano::Plugin
2
+ include PumaCommon
3
+
4
+ def register_hooks
5
+ after 'deploy:finished', 'process_bot:puma:smart_restart'
6
+ end
7
+
8
+ def define_tasks
9
+ eval_rakefile File.expand_path('./puma.rake', __FILE__)
10
+ end
11
+
12
+ def puma_running?
13
+ backend.test("[ -f #{fetch(:puma_pid)} ]") && backend.test(:kill, "-0 $( cat #{fetch(:puma_pid)} )")
14
+ end
15
+
16
+ def run_puma_command(command)
17
+ backend.execute :pumactl, "--control-url 'tcp://127.0.0.1:9293'", "--control-token foobar", "-F #{fetch(:puma_conf)} #{command}"
18
+ end
19
+
20
+ def stop_puma
21
+ run_puma_command("stop")
22
+ end
23
+ end
@@ -0,0 +1,81 @@
1
+ git_plugin = self
2
+
3
+ namespace :load do
4
+ task :defaults do
5
+ set :sidekiq_default_hooks, true
6
+
7
+ set :sidekiq_pid, -> { File.join(shared_path, "tmp", "pids", "sidekiq.pid") }
8
+ set :sidekiq_env, -> { fetch(:rack_env, fetch(:rails_env, fetch(:stage))) }
9
+ set :sidekiq_log, -> { File.join(shared_path, "log", "sidekiq.log") }
10
+ set :sidekiq_timeout, 10
11
+ set :sidekiq_roles, fetch(:sidekiq_role, :app)
12
+ set :sidekiq_processes, 1
13
+ set :sidekiq_options_per_process, nil
14
+ set :sidekiq_user, nil
15
+ # Rbenv, Chruby, and RVM integration
16
+ set :rbenv_map_bins, fetch(:rbenv_map_bins).to_a.concat(%w[sidekiq sidekiqctl])
17
+ set :rvm_map_bins, fetch(:rvm_map_bins).to_a.concat(%w[sidekiq sidekiqctl])
18
+ set :chruby_map_bins, fetch(:chruby_map_bins).to_a.concat(%w[sidekiq sidekiqctl])
19
+ # Bundler integration
20
+ set :bundle_bins, fetch(:bundle_bins).to_a.concat(%w[sidekiq sidekiqctl])
21
+ # Init system integration
22
+ set :init_system, -> { nil }
23
+ # systemd integration
24
+ set :service_unit_name, "sidekiq-#{fetch(:stage)}.service"
25
+ set :upstart_service_name, "sidekiq"
26
+ end
27
+ end
28
+
29
+ namespace :process_bot do
30
+ namespace :sidekiq do
31
+ desc 'Quiet sidekiq (stop fetching new tasks from Redis)'
32
+ task :quiet do
33
+ on roles fetch(:sidekiq_roles) do |role|
34
+ git_plugin.switch_user(role) do
35
+ git_plugin.running_sidekiq_processes.each do |sidekiq_process|
36
+ git_plugin.stop_sidekiq(pid: sidekiq_process.fetch(:pid), signal: "TSTP")
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ desc 'Stop sidekiq (graceful shutdown within timeout, put unfinished tasks back to Redis)'
43
+ task :stop do
44
+ on roles fetch(:sidekiq_roles) do |role|
45
+ git_plugin.switch_user(role) do
46
+ git_plugin.running_sidekiq_processes.each do |sidekiq_process|
47
+ git_plugin.stop_sidekiq(pid: sidekiq_process.fetch(:pid), signal: "TERM")
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ task :stop_after_time do
54
+ on roles fetch(:sidekiq_roles) do |role|
55
+ git_plugin.switch_user(role) do
56
+ git_plugin.running_sidekiq_processes.each do |sidekiq_process|
57
+ git_plugin.stop_sidekiq_after_time(pid: sidekiq_process.fetch(:pid), signal: "TERM")
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ desc 'Start sidekiq'
64
+ task :start do
65
+ on roles fetch(:sidekiq_roles) do |role|
66
+ git_plugin.switch_user(role) do
67
+ fetch(:sidekiq_processes).times do |idx|
68
+ puts "Starting Sidekiq #{idx}"
69
+ git_plugin.start_sidekiq(idx)
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ desc 'Restart sidekiq'
76
+ task :restart do
77
+ invoke! "process_bot:sidekiq:stop"
78
+ invoke! "process_bot:sidekiq:start"
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,7 @@
1
+ module ProcessBot::Capistrano::Sidekiq < Capistrano::Plugin
2
+ include ProcessBot::Sidekiq::Helpers
3
+
4
+ def define_tasks
5
+ eval_rakefile File.expand_path("./sidekiq.rake", __FILE__)
6
+ end
7
+ end
@@ -0,0 +1,131 @@
1
+ module ProcessBot::Sidekiq::Helpers
2
+ def sidekiq_require
3
+ if fetch(:sidekiq_require)
4
+ "--require #{fetch(:sidekiq_require)}"
5
+ end
6
+ end
7
+
8
+ def sidekiq_config
9
+ if fetch(:sidekiq_config)
10
+ "--config #{fetch(:sidekiq_config)}"
11
+ end
12
+ end
13
+
14
+ def sidekiq_concurrency
15
+ if fetch(:sidekiq_concurrency)
16
+ "--concurrency #{fetch(:sidekiq_concurrency)}"
17
+ end
18
+ end
19
+
20
+ def sidekiq_queues
21
+ Array(fetch(:sidekiq_queue)).map do |queue|
22
+ "--queue #{queue}"
23
+ end.join(' ')
24
+ end
25
+
26
+ def sidekiq_logfile
27
+ fetch(:sidekiq_log)
28
+ end
29
+
30
+ def switch_user(role)
31
+ su_user = sidekiq_user(role)
32
+ if su_user == role.user
33
+ yield
34
+ else
35
+ as su_user do
36
+ yield
37
+ end
38
+ end
39
+ end
40
+
41
+ VALID_SIGNALS = ["TERM", "TSTP"]
42
+ def stop_sidekiq(pid:, signal:)
43
+ raise "Invalid PID: #{pid}" unless pid.to_s.match?(/\A\d+\Z/)
44
+ raise "Invalid signal: #{signal}" unless VALID_SIGNALS.include?(signal)
45
+
46
+ backend.execute "kill -#{signal} #{pid}"
47
+ end
48
+
49
+ def stop_sidekiq_after_time(pid:, signal:)
50
+ raise "Invalid PID: #{pid}" unless pid.to_s.match?(/\A\d+\Z/)
51
+ raise "Invalid signal: #{signal}" unless VALID_SIGNALS.include?(signal)
52
+
53
+ time = ENV["STOP_AFTER_TIME"] || fetch(:sidekiq_stop_after_time)
54
+ raise "Invalid time: #{time}" unless time.to_s.match?(/\A\d+\Z/)
55
+
56
+ backend.execute "screen -dmS stopsidekiq#{pid} sleep #{time}; kill -#{signal} #{pid}"
57
+ end
58
+
59
+ def running_sidekiq_processes
60
+ sidekiq_app_name = fetch(:sidekiq_app_name, fetch(:application))
61
+ raise "No :sidekiq_app_name was set" unless sidekiq_app_name
62
+
63
+ begin
64
+ processes_output = backend.capture("ps a | egrep 'sidekiq ([0-9]+\.[0-9]+\.[0-9]+) #{Regexp.escape(sidekiq_app_name)}'")
65
+ rescue SSHKit::Command::Failed
66
+ # Fails when output is empty (when no processes found through grep)
67
+ puts "No Sidekiq processes found"
68
+ return []
69
+ end
70
+
71
+ processes = []
72
+ processes_output.scan(/^\s*(\d+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(.+)$/).each do |process_output|
73
+ sidekiq_pid = process_output[0]
74
+
75
+ processes << {pid: sidekiq_pid}
76
+ end
77
+
78
+ processes
79
+ end
80
+
81
+ def sidekiq_user(role = nil)
82
+ if role.nil?
83
+ fetch(:sidekiq_user)
84
+ else
85
+ properties = role.properties
86
+ properties.fetch(:sidekiq_user) || # local property for sidekiq only
87
+ fetch(:sidekiq_user) ||
88
+ properties.fetch(:run_as) || # global property across multiple capistrano gems
89
+ role.user
90
+ end
91
+ end
92
+
93
+ def expanded_bundle_path
94
+ backend.capture(:echo, SSHKit.config.command_map[:bundle]).strip
95
+ end
96
+
97
+ def start_sidekiq(idx = 0)
98
+ args = []
99
+ args.push "--environment #{fetch(:sidekiq_env)}"
100
+ #args.push "--logfile #{fetch(:sidekiq_log)}" if fetch(:sidekiq_log)
101
+ args.push "--require #{fetch(:sidekiq_require)}" if fetch(:sidekiq_require)
102
+ args.push "--tag #{fetch(:sidekiq_tag)}" if fetch(:sidekiq_tag)
103
+ Array(fetch(:sidekiq_queue)).each do |queue|
104
+ args.push "--queue #{queue}"
105
+ end
106
+ args.push "--config #{fetch(:sidekiq_config)}" if fetch(:sidekiq_config)
107
+ args.push "--concurrency #{fetch(:sidekiq_concurrency)}" if fetch(:sidekiq_concurrency)
108
+ if (process_options = fetch(:sidekiq_options_per_process))
109
+ args.push process_options[idx]
110
+ end
111
+ # use sidekiq_options for special options
112
+ args.push fetch(:sidekiq_options) if fetch(:sidekiq_options)
113
+
114
+ releases = backend.capture(:ls, "-x", releases_path).split
115
+ releases << release_timestamp.to_s if release_timestamp
116
+ releases.uniq
117
+
118
+ latest_release_version = releases.last
119
+ raise "Invalid release timestamp: #{release_timestamp}" unless latest_release_version
120
+
121
+ screen_args = ["-dmS sidekiq-#{idx}-#{latest_release_version}"]
122
+ screen_args << "-L -Logfile #{fetch(:sidekiq_log)}" if fetch(:sidekiq_log)
123
+
124
+ # command = "/usr/bin/tmux new -d -s sidekiq#{idx} '#{SSHKit.config.command_map.prefix[:sidekiq].join(" ")} sidekiq #{args.compact.join(' ')}'"
125
+ command = "/usr/bin/screen #{screen_args.join(" ")} bash -c 'cd #{release_path} && #{SSHKit.config.command_map.prefix[:sidekiq].join(" ")} sidekiq #{args.compact.join(' ')}'"
126
+
127
+ puts "WARNING: A known bug prevents Sidekiq from starting when pty is set (which it is)" if fetch(:pty)
128
+
129
+ backend.execute command
130
+ end
131
+ end
@@ -0,0 +1,5 @@
1
+ class ProcessBot::Process
2
+ def initialize(command)
3
+ @command = command
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ProcessBot
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "process_bot/version"
4
+
5
+ module ProcessBot
6
+ class Error < StandardError; end
7
+ # Your code goes here...
8
+ end
@@ -0,0 +1,4 @@
1
+ module ProcessBot
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,63 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: process_bot
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - kaspernj
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2022-04-03 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Run and control processes.
14
+ email:
15
+ - k@spernj.org
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ".rspec"
21
+ - ".rubocop.yml"
22
+ - CHANGELOG.md
23
+ - Gemfile
24
+ - LICENSE.txt
25
+ - README.md
26
+ - Rakefile
27
+ - lib/process_bot.rb
28
+ - lib/process_bot/capistrano/puma.rake
29
+ - lib/process_bot/capistrano/puma.rb
30
+ - lib/process_bot/capistrano/sidekiq.rake
31
+ - lib/process_bot/capistrano/sidekiq.rb
32
+ - lib/process_bot/capistrano/sidekiq_helpers.rb
33
+ - lib/process_bot/process.rb
34
+ - lib/process_bot/version.rb
35
+ - sig/process_bot.rbs
36
+ homepage: https://github.com/kaspernj/process_bot
37
+ licenses:
38
+ - MIT
39
+ metadata:
40
+ allowed_push_host: https://rubygems.org
41
+ homepage_uri: https://github.com/kaspernj/process_bot
42
+ source_code_uri: https://github.com/kaspernj/process_bot
43
+ changelog_uri: https://github.com/kaspernj/process_bot/blob/master/CHANGELOG.md
44
+ post_install_message:
45
+ rdoc_options: []
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: 2.6.0
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ requirements: []
59
+ rubygems_version: 3.2.32
60
+ signing_key:
61
+ specification_version: 4
62
+ summary: Run and control processes.
63
+ test_files: []