process_bot 0.1.0 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f1bb2b0296a51e0b52b952f49f6724d95d7478181539b75fac529ff3ab3d82de
4
- data.tar.gz: d7ec454928d4e72f40e9a5d5cf05e2fff930cf73de754c47fb344209173294aa
3
+ metadata.gz: 918adb2b2275f38d3b22eb926a5da3b021f667d4a7290a3b0b1d9ea7bb56a2c2
4
+ data.tar.gz: 03c557694d0a91f78f25dc4689f2b99c46b626ebeed5816f8e2e0346e29dd482
5
5
  SHA512:
6
- metadata.gz: c0183b4d9a6f4023393467dc1fb794b2c1fc1c30d481a24b1cbb9728f714be5b2bd1f5e2620c71e60394e59aa79a5fef65f42cc152553007939dda0244635507
7
- data.tar.gz: 8ee7928cf14abedbda792e50653fad619c9ac89196dc7effb0869fcda37141dd637fd63085075e7068cec50d1d903ff69b6216fb446939405654683b000bf4bc
6
+ metadata.gz: 2053d076158df45b723855cee991d47852efb18b34ace65e24005798c7d9d1f1ffff126d1fc7409ba3554e4bfa3bf9855c13dfec5bcfa14ac79aac57d9409acf
7
+ data.tar.gz: 5622bc3069e029485876c7329bd0658b3bf7b3c4b265a40e51421bab04e0b898367f91c864dbe7caf1de93b317e9ba1fcd4787971fd4dc9e28528bf4f14a8df8
data/.rubocop.yml CHANGED
@@ -1,13 +1,189 @@
1
1
  AllCops:
2
+ DisplayCopNames: true
3
+ DisplayStyleGuide: true
4
+ NewCops: enable
2
5
  TargetRubyVersion: 2.6
3
6
 
4
- Style/StringLiterals:
7
+ require:
8
+ - rubocop-performance
9
+ - rubocop-rake
10
+ - rubocop-rspec
11
+
12
+ Layout/AccessModifierIndentation:
13
+ EnforcedStyle: outdent
14
+
15
+ Layout/ArgumentAlignment:
16
+ EnforcedStyle: with_fixed_indentation
17
+
18
+ Layout/CaseIndentation:
19
+ EnforcedStyle: end
20
+
21
+ Layout/EmptyLines:
22
+ Enabled: false
23
+
24
+ Layout/EmptyLinesAroundArguments:
25
+ Enabled: false
26
+
27
+ Layout/EndAlignment:
28
+ EnforcedStyleAlignWith: variable
29
+
30
+ Layout/LineEndStringConcatenationIndentation:
31
+ EnforcedStyle: indented
32
+
33
+ Layout/LineLength:
34
+ Max: 160
35
+
36
+ Layout/MultilineMethodCallIndentation:
37
+ EnforcedStyle: indented
38
+
39
+ Layout/MultilineOperationIndentation:
40
+ EnforcedStyle: indented
41
+
42
+ Layout/ParameterAlignment:
43
+ EnforcedStyle: with_fixed_indentation
44
+
45
+ Layout/RescueEnsureAlignment:
46
+ Enabled: false
47
+
48
+ Layout/SpaceAroundMethodCallOperator:
49
+ Enabled: true
50
+
51
+ Layout/SpaceInsideHashLiteralBraces:
52
+ EnforcedStyle: no_space
53
+
54
+ Lint/MissingSuper:
55
+ Enabled: false
56
+
57
+ Lint/RaiseException:
58
+ Enabled: true
59
+
60
+ Lint/StructNewOverride:
61
+ Enabled: true
62
+
63
+ # Metrics/AbcSize:
64
+ # Max: 25
65
+
66
+ Metrics/BlockLength:
67
+ Enabled: false
68
+
69
+ Metrics/ClassLength:
70
+ Max: 250
71
+
72
+ Metrics/CyclomaticComplexity:
73
+ Max: 12
74
+
75
+ Metrics/MethodLength:
76
+ Max: 50
77
+
78
+ Metrics/ParameterLists:
79
+ CountKeywordArgs: false
80
+
81
+ Metrics/PerceivedComplexity:
82
+ Max: 12
83
+
84
+ RSpec/AnyInstance:
85
+ Enabled: false
86
+
87
+ Style/CaseLikeIf:
88
+ Enabled: false
89
+
90
+ RSpec/ContextWording:
91
+ Enabled: false
92
+
93
+ RSpec/DescribeClass:
94
+ Enabled: false
95
+
96
+ RSpec/DescribedClass:
97
+ Enabled: false
98
+
99
+ RSpec/ExampleLength:
100
+ Enabled: false
101
+
102
+ RSpec/LetSetup:
103
+ Enabled: false
104
+
105
+ RSpec/MessageSpies:
106
+ Enabled: false
107
+
108
+ RSpec/MultipleExpectations:
109
+ Enabled: false
110
+
111
+ RSpec/MultipleMemoizedHelpers:
112
+ Enabled: false
113
+
114
+ RSpec/NamedSubject:
115
+ Enabled: false
116
+
117
+ RSpec/NestedGroups:
118
+ Enabled: false
119
+
120
+ RSpec/StubbedMock:
121
+ Enabled: false
122
+
123
+ Style/ClassAndModuleChildren:
124
+ EnforcedStyle: compact
125
+
126
+ Style/ConditionalAssignment:
127
+ Enabled: false
128
+
129
+ Style/Documentation:
130
+ Enabled: false
131
+
132
+ Style/ExponentialNotation:
133
+ Enabled: true
134
+
135
+ Style/FrozenStringLiteralComment:
136
+ Enabled: false
137
+
138
+ Style/HashAsLastArrayItem:
139
+ Enabled: false
140
+
141
+ Style/HashEachMethods:
142
+ Enabled: true
143
+
144
+ Style/HashTransformKeys:
145
+ Enabled: true
146
+
147
+ Style/HashTransformValues:
5
148
  Enabled: true
149
+
150
+ # Will report offences for many places that are much more readable without using a guard clause
151
+ Style/GuardClause:
152
+ Enabled: false
153
+
154
+ Style/KeywordParametersOrder:
155
+ Enabled: false
156
+
157
+ Style/Lambda:
158
+ Enabled: false
159
+
160
+ Style/LambdaCall:
161
+ Enabled: false
162
+
163
+ Style/MultipleComparison:
164
+ Enabled: false
165
+
166
+ Style/RegexpLiteral:
167
+ Enabled: false
168
+
169
+ Style/StringLiterals:
6
170
  EnforcedStyle: double_quotes
7
171
 
8
172
  Style/StringLiteralsInInterpolation:
173
+ Enabled: false
174
+
175
+ Style/NilComparison:
176
+ Enabled: false
177
+
178
+ Style/SignalException:
179
+ EnforcedStyle: only_raise
180
+
181
+ Style/SymbolArray:
182
+ Enabled: false
183
+
184
+ Style/TrivialAccessors:
185
+ ExactNameMatch: true
9
186
  Enabled: true
10
- EnforcedStyle: double_quotes
11
187
 
12
- Layout/LineLength:
13
- Max: 120
188
+ Style/WordArray:
189
+ Enabled: false
data/Gemfile CHANGED
@@ -5,8 +5,9 @@ source "https://rubygems.org"
5
5
  # Specify your gem's dependencies in process_bot.gemspec
6
6
  gemspec
7
7
 
8
- gem "rake", "~> 13.0"
8
+ gem "rake"
9
+ gem "rspec"
10
+ gem "rubocop"
11
+ gem "string-cases"
9
12
 
10
- gem "rspec", "~> 3.0"
11
-
12
- gem "rubocop", "~> 1.21"
13
+ gem "pry"
data/Gemfile.lock ADDED
@@ -0,0 +1,75 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ process_bot (0.1.2)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ ast (2.4.2)
10
+ coderay (1.1.3)
11
+ diff-lcs (1.5.0)
12
+ json (2.6.2)
13
+ method_source (1.0.0)
14
+ parallel (1.22.1)
15
+ parser (3.1.2.1)
16
+ ast (~> 2.4.1)
17
+ pry (0.14.1)
18
+ coderay (~> 1.1)
19
+ method_source (~> 1.0)
20
+ rainbow (3.1.1)
21
+ rake (13.0.6)
22
+ regexp_parser (2.6.0)
23
+ rexml (3.2.5)
24
+ rspec (3.11.0)
25
+ rspec-core (~> 3.11.0)
26
+ rspec-expectations (~> 3.11.0)
27
+ rspec-mocks (~> 3.11.0)
28
+ rspec-core (3.11.0)
29
+ rspec-support (~> 3.11.0)
30
+ rspec-expectations (3.11.0)
31
+ diff-lcs (>= 1.2.0, < 2.0)
32
+ rspec-support (~> 3.11.0)
33
+ rspec-mocks (3.11.1)
34
+ diff-lcs (>= 1.2.0, < 2.0)
35
+ rspec-support (~> 3.11.0)
36
+ rspec-support (3.11.0)
37
+ rubocop (1.36.0)
38
+ json (~> 2.3)
39
+ parallel (~> 1.10)
40
+ parser (>= 3.1.2.1)
41
+ rainbow (>= 2.2.2, < 4.0)
42
+ regexp_parser (>= 1.8, < 3.0)
43
+ rexml (>= 3.2.5, < 4.0)
44
+ rubocop-ast (>= 1.20.1, < 2.0)
45
+ ruby-progressbar (~> 1.7)
46
+ unicode-display_width (>= 1.4.0, < 3.0)
47
+ rubocop-ast (1.21.0)
48
+ parser (>= 3.1.1.0)
49
+ rubocop-performance (1.15.0)
50
+ rubocop (>= 1.7.0, < 2.0)
51
+ rubocop-ast (>= 0.4.0)
52
+ rubocop-rake (0.6.0)
53
+ rubocop (~> 1.0)
54
+ rubocop-rspec (2.11.1)
55
+ rubocop (~> 1.19)
56
+ ruby-progressbar (1.11.0)
57
+ string-cases (0.0.4)
58
+ unicode-display_width (2.3.0)
59
+
60
+ PLATFORMS
61
+ x86_64-linux
62
+
63
+ DEPENDENCIES
64
+ process_bot!
65
+ pry
66
+ rake
67
+ rspec
68
+ rubocop
69
+ rubocop-performance
70
+ rubocop-rake
71
+ rubocop-rspec
72
+ string-cases
73
+
74
+ BUNDLED WITH
75
+ 2.3.8
data/exe/process_bot ADDED
@@ -0,0 +1,36 @@
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
+ pp options.options
33
+
34
+ ProcessBot::Process
35
+ .new(options)
36
+ .execute!
@@ -0,0 +1,86 @@
1
+ module ProcessBot::Capistrano::Puma::Common
2
+ def puma_switch_user(role)
3
+ user = puma_user(role)
4
+ if user == role.user
5
+ yield
6
+ else
7
+ backend.as user, &block
8
+ end
9
+ end
10
+
11
+ def puma_user(role)
12
+ properties = role.properties
13
+ properties.fetch(:puma_user) || # local property for puma only
14
+ fetch(:puma_user) ||
15
+ properties.fetch(:run_as) || # global property across multiple capistrano gems
16
+ role.user
17
+ end
18
+
19
+ def puma_bind
20
+ Array(fetch(:puma_bind)).collect do |bind|
21
+ "bind '#{bind}'"
22
+ end.join("\n")
23
+ end
24
+
25
+ def compiled_template_puma(from, role)
26
+ @role = role
27
+ file = [
28
+ "lib/capistrano/templates/#{from}-#{role.hostname}-#{fetch(:stage)}.rb",
29
+ "lib/capistrano/templates/#{from}-#{role.hostname}.rb",
30
+ "lib/capistrano/templates/#{from}-#{fetch(:stage)}.rb",
31
+ "lib/capistrano/templates/#{from}.rb.erb",
32
+ "lib/capistrano/templates/#{from}.rb",
33
+ "lib/capistrano/templates/#{from}.erb",
34
+ "config/deploy/templates/#{from}.rb.erb",
35
+ "config/deploy/templates/#{from}.rb",
36
+ "config/deploy/templates/#{from}.erb",
37
+ File.expand_path("../templates/#{from}.erb", __FILE__),
38
+ File.expand_path("../templates/#{from}.rb.erb", __FILE__)
39
+ ].detect { |path| File.file?(path) }
40
+ erb = File.read(file)
41
+ StringIO.new(ERB.new(erb, trim_mode: "-").result(binding))
42
+ end
43
+
44
+ def template_puma(from, to, role)
45
+ backend.upload! compiled_template_puma(from, role), to
46
+ end
47
+
48
+ PumaBind = Struct.new(:full_address, :kind, :address) do
49
+ def unix?
50
+ kind == :unix
51
+ end
52
+
53
+ def ssl?
54
+ kind == :ssl
55
+ end
56
+
57
+ def tcp
58
+ kind == :tcp || ssl?
59
+ end
60
+
61
+ def local
62
+ if unix?
63
+ self
64
+ else
65
+ PumaBind.new(
66
+ localize_address(full_address),
67
+ kind,
68
+ localize_address(address)
69
+ )
70
+ end
71
+ end
72
+
73
+ private
74
+
75
+ def localize_address(address)
76
+ address.gsub(/0\.0\.0\.0(.+)/, "127.0.0.1\\1")
77
+ end
78
+ end
79
+
80
+ def puma_binds
81
+ Array(fetch(:puma_bind)).map do |m|
82
+ etype, address = /(tcp|unix|ssl):\/{1,2}(.+)/.match(m).captures
83
+ PumaBind.new(m, etype.to_sym, address)
84
+ end
85
+ end
86
+ end
@@ -6,7 +6,7 @@ namespace :process_bot do
6
6
  task :start do
7
7
  on roles(fetch(:puma_role)) do |role|
8
8
  git_plugin.puma_switch_user(role) do
9
- if test "[ -f #{fetch(:puma_pid)} ]" and test :kill, "-0 $( cat #{fetch(:puma_pid)} )"
9
+ if test("[ -f #{fetch(:puma_pid)} ]") && test(:kill, "-0 $( cat #{fetch(:puma_pid)} )")
10
10
  info "Puma is already running"
11
11
  else
12
12
  within current_path do
@@ -24,7 +24,9 @@ namespace :process_bot do
24
24
  "--control-token foobar"
25
25
  ]
26
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(" ")}'"
27
+ command = "/usr/bin/screen -dmS puma-#{latest_release_version} " \
28
+ "bash -c 'cd #{release_path} && #{SSHKit.config.command_map.prefix[:puma].join(" ")} puma #{puma_args.join(" ")}'"
29
+
28
30
  execute command
29
31
  end
30
32
  end
@@ -36,7 +38,7 @@ namespace :process_bot do
36
38
  %w[halt stop status].map do |command|
37
39
  desc "#{command} puma"
38
40
  task command do
39
- on roles (fetch(:puma_role)) do |role|
41
+ on roles(fetch(:puma_role)) do |role|
40
42
  within current_path do
41
43
  git_plugin.puma_switch_user(role) do
42
44
  with rack_env: fetch(:puma_env) do
@@ -48,7 +50,7 @@ namespace :process_bot do
48
50
  execute :rm, fetch(:puma_pid)
49
51
  end
50
52
  else
51
- #pid file not found, so puma is probably not running or it using another pidfile
53
+ # pid file not found, so puma is probably not running or it using another pidfile
52
54
  warn "Puma not running"
53
55
  end
54
56
  end
@@ -61,12 +63,12 @@ namespace :process_bot do
61
63
  %w[phased-restart restart].map do |command|
62
64
  desc "#{command} puma"
63
65
  task command do
64
- on roles (fetch(:puma_role)) do |role|
66
+ on roles(fetch(:puma_role)) do |role|
65
67
  within current_path do
66
68
  git_plugin.puma_switch_user(role) do
67
69
  with rack_env: fetch(:puma_env) do
68
70
  if git_plugin.puma_running?
69
- # NOTE pid exist but state file is nonsense, so ignore that case
71
+ # NOTE: pid exist but state file is nonsense, so ignore that case
70
72
  git_plugin.run_puma_command(command)
71
73
  else
72
74
  # Puma is not running or state file is not present : Run it
@@ -79,6 +81,7 @@ namespace :process_bot do
79
81
  end
80
82
  end
81
83
 
84
+ desc "Restarts Puma phased if using workers and preload and otherwise a normal restart."
82
85
  task :smart_restart do
83
86
  if !fetch(:puma_preload_app) && fetch(:puma_workers, 0).to_i > 1
84
87
  invoke "process_bot:puma:phased-restart"
@@ -1,12 +1,10 @@
1
- module ProcessBot::Capistrano::Puma < Capistrano::Plugin
2
- include PumaCommon
1
+ class ProcessBot::Capistrano::Puma < Capistrano::Plugin
2
+ autoload :Common, "#{__dir__}/puma/common"
3
3
 
4
- def register_hooks
5
- after 'deploy:finished', 'process_bot:puma:smart_restart'
6
- end
4
+ include ::ProcessBot::Capistrano::Puma::Common
7
5
 
8
6
  def define_tasks
9
- eval_rakefile File.expand_path('./puma.rake', __FILE__)
7
+ eval_rakefile File.expand_path("./puma.rake", __dir__)
10
8
  end
11
9
 
12
10
  def puma_running?
@@ -1,6 +1,7 @@
1
1
  git_plugin = self
2
2
 
3
3
  namespace :load do
4
+ desc "Default variables for Sidekiq"
4
5
  task :defaults do
5
6
  set :sidekiq_default_hooks, true
6
7
 
@@ -18,17 +19,12 @@ namespace :load do
18
19
  set :chruby_map_bins, fetch(:chruby_map_bins).to_a.concat(%w[sidekiq sidekiqctl])
19
20
  # Bundler integration
20
21
  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
22
  end
27
23
  end
28
24
 
29
25
  namespace :process_bot do
30
26
  namespace :sidekiq do
31
- desc 'Quiet sidekiq (stop fetching new tasks from Redis)'
27
+ desc "Quiet sidekiq (stop fetching new tasks from Redis)"
32
28
  task :quiet do
33
29
  on roles fetch(:sidekiq_roles) do |role|
34
30
  git_plugin.switch_user(role) do
@@ -39,7 +35,7 @@ namespace :process_bot do
39
35
  end
40
36
  end
41
37
 
42
- desc 'Stop sidekiq (graceful shutdown within timeout, put unfinished tasks back to Redis)'
38
+ desc "Stop Sidekiq (graceful shutdown within timeout, put unfinished tasks back to Redis)"
43
39
  task :stop do
44
40
  on roles fetch(:sidekiq_roles) do |role|
45
41
  git_plugin.switch_user(role) do
@@ -50,6 +46,7 @@ namespace :process_bot do
50
46
  end
51
47
  end
52
48
 
49
+ desc "Stops Sidekiq after a set amount of time"
53
50
  task :stop_after_time do
54
51
  on roles fetch(:sidekiq_roles) do |role|
55
52
  git_plugin.switch_user(role) do
@@ -60,7 +57,7 @@ namespace :process_bot do
60
57
  end
61
58
  end
62
59
 
63
- desc 'Start sidekiq'
60
+ desc "Start sidekiq"
64
61
  task :start do
65
62
  on roles fetch(:sidekiq_roles) do |role|
66
63
  git_plugin.switch_user(role) do
@@ -72,7 +69,7 @@ namespace :process_bot do
72
69
  end
73
70
  end
74
71
 
75
- desc 'Restart sidekiq'
72
+ desc "Restart sidekiq"
76
73
  task :restart do
77
74
  invoke! "process_bot:sidekiq:stop"
78
75
  invoke! "process_bot:sidekiq:start"
@@ -1,7 +1,9 @@
1
- module ProcessBot::Capistrano::Sidekiq < Capistrano::Plugin
2
- include ProcessBot::Sidekiq::Helpers
1
+ require_relative "sidekiq_helpers"
2
+
3
+ class ProcessBot::Capistrano::Sidekiq < Capistrano::Plugin
4
+ include ProcessBot::Capistrano::SidekiqHelpers
3
5
 
4
6
  def define_tasks
5
- eval_rakefile File.expand_path("./sidekiq.rake", __FILE__)
7
+ eval_rakefile File.expand_path("./sidekiq.rake", __dir__)
6
8
  end
7
9
  end
@@ -1,44 +1,36 @@
1
- module ProcessBot::Sidekiq::Helpers
1
+ module ProcessBot::Capistrano::SidekiqHelpers
2
2
  def sidekiq_require
3
- if fetch(:sidekiq_require)
4
- "--require #{fetch(:sidekiq_require)}"
5
- end
3
+ "--require #{fetch(:sidekiq_require)}" if fetch(:sidekiq_require)
6
4
  end
7
5
 
8
6
  def sidekiq_config
9
- if fetch(:sidekiq_config)
10
- "--config #{fetch(:sidekiq_config)}"
11
- end
7
+ "--config #{fetch(:sidekiq_config)}" if fetch(:sidekiq_config)
12
8
  end
13
9
 
14
10
  def sidekiq_concurrency
15
- if fetch(:sidekiq_concurrency)
16
- "--concurrency #{fetch(:sidekiq_concurrency)}"
17
- end
11
+ "--concurrency #{fetch(:sidekiq_concurrency)}" if fetch(:sidekiq_concurrency)
18
12
  end
19
13
 
20
14
  def sidekiq_queues
21
15
  Array(fetch(:sidekiq_queue)).map do |queue|
22
16
  "--queue #{queue}"
23
- end.join(' ')
17
+ end.join(" ")
24
18
  end
25
19
 
26
20
  def sidekiq_logfile
27
21
  fetch(:sidekiq_log)
28
22
  end
29
23
 
30
- def switch_user(role)
24
+ def switch_user(role, &block)
31
25
  su_user = sidekiq_user(role)
32
26
  if su_user == role.user
33
27
  yield
34
28
  else
35
- as su_user do
36
- yield
37
- end
29
+ as su_user, &block
38
30
  end
39
31
  end
40
32
 
41
- VALID_SIGNALS = ["TERM", "TSTP"]
33
+ VALID_SIGNALS = ["TERM", "TSTP"].freeze
42
34
  def stop_sidekiq(pid:, signal:)
43
35
  raise "Invalid PID: #{pid}" unless pid.to_s.match?(/\A\d+\Z/)
44
36
  raise "Invalid signal: #{signal}" unless VALID_SIGNALS.include?(signal)
@@ -53,7 +45,7 @@ module ProcessBot::Sidekiq::Helpers
53
45
  time = ENV["STOP_AFTER_TIME"] || fetch(:sidekiq_stop_after_time)
54
46
  raise "Invalid time: #{time}" unless time.to_s.match?(/\A\d+\Z/)
55
47
 
56
- backend.execute "screen -dmS stopsidekiq#{pid} sleep #{time}; kill -#{signal} #{pid}"
48
+ backend.execute "screen -dmS stopsidekiq#{pid} bash -c \"sleep #{time} && kill -#{signal} #{pid}\""
57
49
  end
58
50
 
59
51
  def running_sidekiq_processes
@@ -94,23 +86,7 @@ module ProcessBot::Sidekiq::Helpers
94
86
  backend.capture(:echo, SSHKit.config.command_map[:bundle]).strip
95
87
  end
96
88
 
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
-
89
+ def start_sidekiq(idx = 0) # rubocop:disable Metrics/AbcSize
114
90
  releases = backend.capture(:ls, "-x", releases_path).split
115
91
  releases << release_timestamp.to_s if release_timestamp
116
92
  releases.uniq
@@ -118,13 +94,34 @@ module ProcessBot::Sidekiq::Helpers
118
94
  latest_release_version = releases.last
119
95
  raise "Invalid release timestamp: #{release_timestamp}" unless latest_release_version
120
96
 
97
+ args = [
98
+ "--id", "sidekiq-#{latest_release_version}-#{idx}",
99
+ "--handler", "sidekiq",
100
+ "--bundle-prefix", SSHKit.config.command_map.prefix[:bundle].join(" "),
101
+ "--sidekiq-environment", fetch(:sidekiq_env),
102
+ "--port", 7050 + idx
103
+ ]
104
+ args += ["--log-file-path", fetch(:sidekiq_log)] if fetch(:sidekiq_log)
105
+ args += ["--sidekiq-require", fetch(:sidekiq_require)] if fetch(:sidekiq_require)
106
+ args += ["--sidekiq-tag", fetch(:sidekiq_tag)] if fetch(:sidekiq_tag)
107
+ args += ["--sidekiq-queues", Array(fetch(:sidekiq_queue)).join(",")] if fetch(:sidekiq_queue)
108
+ args += ["--sidekiq-config", fetch(:sidekiq_config)] if fetch(:sidekiq_config)
109
+ args += ["--sidekiq-concurrency", fetch(:sidekiq_concurrency)] if fetch(:sidekiq_concurrency)
110
+ if (process_options = fetch(:sidekiq_options_per_process))
111
+ args += process_options[idx]
112
+ end
113
+ args += fetch(:sidekiq_options) if fetch(:sidekiq_options)
114
+
121
115
  screen_args = ["-dmS sidekiq-#{idx}-#{latest_release_version}"]
122
116
  screen_args << "-L -Logfile #{fetch(:sidekiq_log)}" if fetch(:sidekiq_log)
123
117
 
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(' ')}'"
118
+ process_bot_args = args.compact.map { |arg| "\"#{arg}\"" }
119
+
120
+ 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(" ")}'"
126
122
 
127
123
  puts "WARNING: A known bug prevents Sidekiq from starting when pty is set (which it is)" if fetch(:pty)
124
+ puts "ProcessBot Sidekiq command: #{command}"
128
125
 
129
126
  backend.execute command
130
127
  end
@@ -0,0 +1,4 @@
1
+ class ProcessBot::Capistrano
2
+ autoload :Puma, "#{__dir__}/capistrano/puma"
3
+ autoload :Sidekiq, "#{__dir__}/capistrano/sidekiq"
4
+ end
@@ -0,0 +1,39 @@
1
+ class ProcessBot::ControlSocket
2
+ attr_reader :options, :process, :server
3
+
4
+ def initialize(options:, process:)
5
+ @options = options
6
+ @process = process
7
+ end
8
+
9
+ def port
10
+ options.fetch(:port).to_i
11
+ end
12
+
13
+ def start
14
+ require "socket"
15
+
16
+ @server = TCPServer.new(port)
17
+ end
18
+
19
+ def run_client_loop
20
+ Thread.new do
21
+ client = server.accept
22
+
23
+ Thread.new do
24
+ handle_client(client)
25
+ end
26
+ end
27
+ end
28
+
29
+ def handle_client(client)
30
+ command = JSON.parse(client.gets)
31
+ type = command.fetch("type")
32
+
33
+ if type == "stop"
34
+ process.stop
35
+ else
36
+ client.puts(JSON.generate(type: "error", message: "Unknown type: #{type}"))
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,22 @@
1
+ class ProcessBot::Logger
2
+ attr_reader :fp_log, :options
3
+
4
+ def initialize(options:)
5
+ @options = options
6
+
7
+ open_file
8
+ end
9
+
10
+ def log(output)
11
+ fp_log&.write(output)
12
+ fp_log&.flush
13
+ end
14
+
15
+ def log_file_path
16
+ options.fetch(:log_file_path)
17
+ end
18
+
19
+ def open_file
20
+ @fp_log = File.open(log_file_path, "a")
21
+ end
22
+ end
@@ -0,0 +1,21 @@
1
+ class ProcessBot::Options
2
+ attr_reader :options
3
+
4
+ def initialize(options = {})
5
+ @options = options
6
+ end
7
+
8
+ def fetch(*args, **opts, &blk)
9
+ options.fetch(*args, **opts, &blk)
10
+ end
11
+
12
+ def present?(key)
13
+ return true if options.key?(key) && options[key]
14
+
15
+ false
16
+ end
17
+
18
+ def set(key, value)
19
+ options[key] = value
20
+ end
21
+ end
@@ -0,0 +1,55 @@
1
+ class ProcessBot::Process::Handlers::Sidekiq
2
+ attr_reader :options
3
+
4
+ def initialize(options)
5
+ @options = options
6
+
7
+ set_defaults
8
+ end
9
+
10
+ def fetch(*args, **opts)
11
+ options.fetch(*args, **opts)
12
+ end
13
+
14
+ def set_option(key, value)
15
+ raise "Unknown option for Sidekiq handler: #{key}" unless options.key?(key)
16
+
17
+ set(key, value)
18
+ end
19
+
20
+ def set(*args, **opts)
21
+ options.set(*args, **opts)
22
+ end
23
+
24
+ def set_defaults
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
34
+ args = []
35
+
36
+ options.options.each do |key, value|
37
+ if (match = key.to_s.match(/\Asidekiq-(.+)\Z/))
38
+ sidekiq_key = match[1]
39
+
40
+ if sidekiq_key == "queue"
41
+ value.split(",").each do |queue|
42
+ args.push "--queue #{value}"
43
+ end
44
+ else
45
+ args.push "--#{sidekiq_key} #{value}"
46
+ end
47
+ end
48
+ end
49
+
50
+ command = ""
51
+ command << "#{options.fetch(:bundle_prefix)} " if options.present?(:bundle_prefix)
52
+ command << "bundle exec sidekiq #{args.compact.join(' ')}"
53
+ command
54
+ end
55
+ end
@@ -0,0 +1,3 @@
1
+ class ProcessBot::Process::Handlers
2
+ autoload :Sidekiq, "#{__dir__}/handlers/sidekiq"
3
+ end
@@ -0,0 +1,55 @@
1
+ class ProcessBot::Process::Runner
2
+ attr_reader :command, :exit_status, :logger, :monitor, :options, :stop_time
3
+
4
+ def initialize(command:, logger:, options:)
5
+ @command = command
6
+ @logger = logger
7
+ @monitor = Monitor.new
8
+ @options = options
9
+ @output = []
10
+ end
11
+
12
+ def output(output:, type:) # rubocop:disable Lint/UnusedMethodArgument
13
+ logger.log(output)
14
+ end
15
+
16
+ def run # rubocop:disable Metrics/AbcSize
17
+ @start_time = Time.new
18
+ stderr_reader, stderr_writer = IO.pipe
19
+
20
+ require "pty"
21
+
22
+ PTY.spawn(command, err: stderr_writer.fileno) do |stdout, _stdin, pid|
23
+ @pid = pid
24
+ logger.log "Command running with PID #{pid}: #{command}"
25
+
26
+ stdout_reader_thread = Thread.new do
27
+ stdout.each_char do |chunk|
28
+ monitor.synchronize do
29
+ output(type: :stdout, output: chunk)
30
+ end
31
+ end
32
+ rescue Errno::EIO
33
+ # Process done
34
+ ensure
35
+ status = Process::Status.wait(@pid, 0)
36
+
37
+ @exit_status = status.exitstatus
38
+ stderr_writer.close
39
+ end
40
+
41
+ stderr_reader_thread = Thread.new do
42
+ stderr_reader.each_char do |chunk|
43
+ monitor.synchronize do
44
+ output(type: :stderr, output: chunk)
45
+ end
46
+ end
47
+ end
48
+
49
+ stdout_reader_thread.join
50
+ stderr_reader_thread.join
51
+
52
+ @stop_time = Time.new
53
+ end
54
+ end
55
+ end
@@ -1,5 +1,52 @@
1
1
  class ProcessBot::Process
2
- def initialize(command)
3
- @command = command
2
+ autoload :Handlers, "#{__dir__}/process/handlers"
3
+ autoload :Runner, "#{__dir__}/process/runner"
4
+
5
+ attr_reader :options, :stopped
6
+
7
+ def initialize(options)
8
+ @options = options
9
+ @stopped = false
10
+ end
11
+
12
+ def logger
13
+ @logger ||= ProcessBot::Logger.new(options: options)
14
+ end
15
+
16
+ def start_control_socket
17
+ @control_socket = ProcessBot::ControlSocket.new(options: options, process: self)
18
+ @control_socket.start
19
+ end
20
+
21
+ def stop
22
+ @stopped = true
23
+ end
24
+
25
+ def handler_class
26
+ @handler_class ||= begin
27
+ require_relative "process/handlers/#{options.fetch(:handler)}"
28
+ ProcessBot::Process::Handlers.const_get(StringCases.snake_to_camel(options.fetch(:handler)))
29
+ end
30
+ end
31
+
32
+ def execute!
33
+ start_control_socket
34
+
35
+ loop do
36
+ run
37
+
38
+ if stopped
39
+ break
40
+ else
41
+ puts "Process stopped - starting again after 1 sec"
42
+ sleep 1
43
+ end
44
+ end
45
+ end
46
+
47
+ def run
48
+ handler_instance = handler_class.new(options)
49
+ runner = ProcessBot::Process::Runner.new(command: handler_instance.command, logger: logger, options: options)
50
+ runner.run
4
51
  end
5
52
  end
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  module ProcessBot
4
- VERSION = "0.1.0"
2
+ VERSION = "0.1.2"
5
3
  end
data/lib/process_bot.rb CHANGED
@@ -1,8 +1,11 @@
1
- # frozen_string_literal: true
2
-
3
1
  require_relative "process_bot/version"
4
2
 
5
3
  module ProcessBot
6
4
  class Error < StandardError; end
7
- # Your code goes here...
5
+
6
+ autoload :Capistrano, "#{__dir__}/process_bot/capistrano"
7
+ autoload :ControlSocket, "#{__dir__}/process_bot/control_socket"
8
+ autoload :Logger, "#{__dir__}/process_bot/logger"
9
+ autoload :Options, "#{__dir__}/process_bot/options"
10
+ autoload :Process, "#{__dir__}/process_bot/process"
8
11
  end
data/peak_flow.yml ADDED
@@ -0,0 +1,4 @@
1
+ rvm: true
2
+ script:
3
+ - bundle exec rspec
4
+ - bundle exec rubocop
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/process_bot/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "process_bot"
7
+ spec.version = ProcessBot::VERSION
8
+ spec.authors = ["kaspernj"]
9
+ spec.email = ["k@spernj.org"]
10
+
11
+ spec.summary = "Run and control processes."
12
+ spec.description = "Run and control processes."
13
+ spec.homepage = "https://github.com/kaspernj/process_bot"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 2.6.0"
16
+
17
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
18
+
19
+ spec.metadata["homepage_uri"] = spec.homepage
20
+ spec.metadata["source_code_uri"] = "https://github.com/kaspernj/process_bot"
21
+ spec.metadata["changelog_uri"] = "https://github.com/kaspernj/process_bot/blob/master/CHANGELOG.md"
22
+
23
+ # Specify which files should be added to the gem when it is released.
24
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
25
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
26
+ `git ls-files -z`.split("\x0").reject do |f|
27
+ (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
28
+ end
29
+ end
30
+ spec.bindir = "exe"
31
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
32
+ spec.require_paths = ["lib"]
33
+
34
+ # Uncomment to register a new dependency of your gem
35
+ # spec.add_dependency "example-gem", "~> 1.0"
36
+
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
+ spec.metadata["rubygems_mfa_required"] = "true"
45
+ end
metadata CHANGED
@@ -1,19 +1,76 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: process_bot
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - kaspernj
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-04-03 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2022-10-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rubocop
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
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'
13
69
  description: Run and control processes.
14
70
  email:
15
71
  - k@spernj.org
16
- executables: []
72
+ executables:
73
+ - process_bot
17
74
  extensions: []
18
75
  extra_rdoc_files: []
19
76
  files:
@@ -21,17 +78,29 @@ files:
21
78
  - ".rubocop.yml"
22
79
  - CHANGELOG.md
23
80
  - Gemfile
81
+ - Gemfile.lock
24
82
  - LICENSE.txt
25
83
  - README.md
26
84
  - Rakefile
85
+ - exe/process_bot
27
86
  - lib/process_bot.rb
87
+ - lib/process_bot/capistrano.rb
28
88
  - lib/process_bot/capistrano/puma.rake
29
89
  - lib/process_bot/capistrano/puma.rb
90
+ - lib/process_bot/capistrano/puma/common.rb
30
91
  - lib/process_bot/capistrano/sidekiq.rake
31
92
  - lib/process_bot/capistrano/sidekiq.rb
32
93
  - lib/process_bot/capistrano/sidekiq_helpers.rb
94
+ - lib/process_bot/control_socket.rb
95
+ - lib/process_bot/logger.rb
96
+ - lib/process_bot/options.rb
33
97
  - lib/process_bot/process.rb
98
+ - lib/process_bot/process/handlers.rb
99
+ - lib/process_bot/process/handlers/sidekiq.rb
100
+ - lib/process_bot/process/runner.rb
34
101
  - lib/process_bot/version.rb
102
+ - peak_flow.yml
103
+ - process_bot.gemspec
35
104
  - sig/process_bot.rbs
36
105
  homepage: https://github.com/kaspernj/process_bot
37
106
  licenses:
@@ -41,6 +110,7 @@ metadata:
41
110
  homepage_uri: https://github.com/kaspernj/process_bot
42
111
  source_code_uri: https://github.com/kaspernj/process_bot
43
112
  changelog_uri: https://github.com/kaspernj/process_bot/blob/master/CHANGELOG.md
113
+ rubygems_mfa_required: 'true'
44
114
  post_install_message:
45
115
  rdoc_options: []
46
116
  require_paths:
@@ -56,7 +126,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
56
126
  - !ruby/object:Gem::Version
57
127
  version: '0'
58
128
  requirements: []
59
- rubygems_version: 3.2.32
129
+ rubygems_version: 3.3.7
60
130
  signing_key:
61
131
  specification_version: 4
62
132
  summary: Run and control processes.