process_bot 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f1bb2b0296a51e0b52b952f49f6724d95d7478181539b75fac529ff3ab3d82de
4
- data.tar.gz: d7ec454928d4e72f40e9a5d5cf05e2fff930cf73de754c47fb344209173294aa
3
+ metadata.gz: f81fc2f58388142abc41d1cd0eb5768ea69c1e26b46e2c87ee4f859f19b4d6d4
4
+ data.tar.gz: 754e4510903fb877ccd9cdea055c0b0b4fef87697c0250ebb9a0d9f42cd18fb5
5
5
  SHA512:
6
- metadata.gz: c0183b4d9a6f4023393467dc1fb794b2c1fc1c30d481a24b1cbb9728f714be5b2bd1f5e2620c71e60394e59aa79a5fef65f42cc152553007939dda0244635507
7
- data.tar.gz: 8ee7928cf14abedbda792e50653fad619c9ac89196dc7effb0869fcda37141dd637fd63085075e7068cec50d1d903ff69b6216fb446939405654683b000bf4bc
6
+ metadata.gz: d2bf3cf91868d4c73f31bebe07c9a87d43b6419ed1332410d757f5fb13598a3a6af982311b3c33f654aece52bca325f0ea4d8c9c3fde752c0997b631660b4eb9
7
+ data.tar.gz: 0f6f9fe487f42ce26d709ce88bb01426433f4e985c09e98d88c5f034824c290b0fa974e1e9e9d307c9c339ca888cac06073874661c7a7e6abb36407729430a99
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.1)
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
@@ -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.1"
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|exe|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,15 +1,71 @@
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.1
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
@@ -21,17 +77,28 @@ files:
21
77
  - ".rubocop.yml"
22
78
  - CHANGELOG.md
23
79
  - Gemfile
80
+ - Gemfile.lock
24
81
  - LICENSE.txt
25
82
  - README.md
26
83
  - Rakefile
27
84
  - lib/process_bot.rb
85
+ - lib/process_bot/capistrano.rb
28
86
  - lib/process_bot/capistrano/puma.rake
29
87
  - lib/process_bot/capistrano/puma.rb
88
+ - lib/process_bot/capistrano/puma/common.rb
30
89
  - lib/process_bot/capistrano/sidekiq.rake
31
90
  - lib/process_bot/capistrano/sidekiq.rb
32
91
  - lib/process_bot/capistrano/sidekiq_helpers.rb
92
+ - lib/process_bot/control_socket.rb
93
+ - lib/process_bot/logger.rb
94
+ - lib/process_bot/options.rb
33
95
  - lib/process_bot/process.rb
96
+ - lib/process_bot/process/handlers.rb
97
+ - lib/process_bot/process/handlers/sidekiq.rb
98
+ - lib/process_bot/process/runner.rb
34
99
  - lib/process_bot/version.rb
100
+ - peak_flow.yml
101
+ - process_bot.gemspec
35
102
  - sig/process_bot.rbs
36
103
  homepage: https://github.com/kaspernj/process_bot
37
104
  licenses:
@@ -41,6 +108,7 @@ metadata:
41
108
  homepage_uri: https://github.com/kaspernj/process_bot
42
109
  source_code_uri: https://github.com/kaspernj/process_bot
43
110
  changelog_uri: https://github.com/kaspernj/process_bot/blob/master/CHANGELOG.md
111
+ rubygems_mfa_required: 'true'
44
112
  post_install_message:
45
113
  rdoc_options: []
46
114
  require_paths:
@@ -56,7 +124,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
56
124
  - !ruby/object:Gem::Version
57
125
  version: '0'
58
126
  requirements: []
59
- rubygems_version: 3.2.32
127
+ rubygems_version: 3.3.7
60
128
  signing_key:
61
129
  specification_version: 4
62
130
  summary: Run and control processes.