honcho 1.0.0
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 +7 -0
- data/bin/honcho +5 -0
- data/lib/honcho/adapters/base.rb +26 -0
- data/lib/honcho/adapters/resque.rb +22 -0
- data/lib/honcho/adapters/sidekiq.rb +25 -0
- data/lib/honcho/adapters.rb +3 -0
- data/lib/honcho.rb +181 -0
- metadata +66 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3429cf3e359e2fc30f07682a77b4759205a9da00
|
4
|
+
data.tar.gz: 03fa822e64a34857dae9ca770abae29a80a17e05
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: acb22985e32a0e7dbca8aaa5104b4de987f3b72c4eb9102d3cf8e596217918666fe685d9dbf3400755a2abcd59928ba9425af2b16180a691454b7415986649bd
|
7
|
+
data.tar.gz: 046b0e51b48ee9f5a5a8983c7b5bc3f0ff270f1b9c92fce0fe3f3aa105544843051d6d85b4efdd257063e9ccd1340fadf7544a48af0b9624f4f3ddd0db096ab6
|
data/bin/honcho
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
class Honcho
|
2
|
+
module Adapters
|
3
|
+
class Base
|
4
|
+
def initialize(config:, redis:)
|
5
|
+
@redis = redis
|
6
|
+
@config = config
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :config, :redis
|
10
|
+
|
11
|
+
def run?
|
12
|
+
work_to_do? || work_being_done?
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def work_to_do?
|
18
|
+
fail NotImplementedError, "please define #{this.class.name}##{__method__}"
|
19
|
+
end
|
20
|
+
|
21
|
+
def work_being_done?
|
22
|
+
fail NotImplementedError, "please define #{this.class.name}##{__method__}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class Honcho
|
2
|
+
module Adapters
|
3
|
+
class Resque < Base
|
4
|
+
private
|
5
|
+
|
6
|
+
def work_to_do?
|
7
|
+
queues = redis.smembers("#{namespace}:queues")
|
8
|
+
counts = queues.map { |q| redis.llen("#{namespace}:queue:#{q}") }
|
9
|
+
counts.any?(&:nonzero?)
|
10
|
+
end
|
11
|
+
|
12
|
+
def work_being_done?
|
13
|
+
# No way to tell via redis if work is being done in resque? Booo.
|
14
|
+
false
|
15
|
+
end
|
16
|
+
|
17
|
+
def namespace
|
18
|
+
config.fetch('namespace')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class Honcho
|
2
|
+
module Adapters
|
3
|
+
class Sidekiq < Base
|
4
|
+
private
|
5
|
+
|
6
|
+
def work_to_do?
|
7
|
+
queues = redis.keys("#{namespace}:queue:*")
|
8
|
+
counts = queues.map { |q| redis.llen(q) }
|
9
|
+
counts.any?(&:nonzero?)
|
10
|
+
end
|
11
|
+
|
12
|
+
def work_being_done?
|
13
|
+
processes = redis.smembers("#{namespace}:processes")
|
14
|
+
counts = processes.map do |process|
|
15
|
+
redis.hget("#{namespace}:#{process}", 'busy').to_i
|
16
|
+
end
|
17
|
+
counts.any?(&:nonzero?)
|
18
|
+
end
|
19
|
+
|
20
|
+
def namespace
|
21
|
+
config.fetch('namespace')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/honcho.rb
ADDED
@@ -0,0 +1,181 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'redis'
|
4
|
+
require 'sys/proctable'
|
5
|
+
require 'time'
|
6
|
+
require 'stringio'
|
7
|
+
require 'yaml'
|
8
|
+
require_relative './honcho/adapters'
|
9
|
+
|
10
|
+
Thread.abort_on_exception = true
|
11
|
+
|
12
|
+
class Honcho
|
13
|
+
COLORS = {
|
14
|
+
red: 31,
|
15
|
+
green: 32,
|
16
|
+
yellow: 33,
|
17
|
+
blue: 34,
|
18
|
+
magenta: 35,
|
19
|
+
cyan: 36,
|
20
|
+
bright_black: 30,
|
21
|
+
bright_red: 31,
|
22
|
+
bright_green: 32,
|
23
|
+
bright_yellow: 33,
|
24
|
+
bright_blue: 34,
|
25
|
+
bright_magenta: 35,
|
26
|
+
bright_cyan: 36,
|
27
|
+
bright_white: 37
|
28
|
+
}.freeze
|
29
|
+
|
30
|
+
def initialize
|
31
|
+
@running = {}
|
32
|
+
@stopping = {}
|
33
|
+
@redis = Redis.new
|
34
|
+
@adapters = build_adapters
|
35
|
+
@colors = assign_colors
|
36
|
+
end
|
37
|
+
|
38
|
+
attr_reader :adapters, :running, :stopping, :redis, :colors
|
39
|
+
|
40
|
+
def run
|
41
|
+
trap(:INT) { term_all && exit }
|
42
|
+
trap(:TERM) { term_all && exit }
|
43
|
+
loop do
|
44
|
+
check_for_work
|
45
|
+
sleep(interval)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def apps
|
52
|
+
config['apps']
|
53
|
+
end
|
54
|
+
|
55
|
+
def command_template
|
56
|
+
config['command_template'] || '%s'
|
57
|
+
end
|
58
|
+
|
59
|
+
def stop_delay
|
60
|
+
config['stop_delay'] || 30
|
61
|
+
end
|
62
|
+
|
63
|
+
def interval
|
64
|
+
config['interval'] || 2
|
65
|
+
end
|
66
|
+
|
67
|
+
def config
|
68
|
+
@config ||= YAML.load_file('honcho.yml')
|
69
|
+
end
|
70
|
+
|
71
|
+
def check_for_work
|
72
|
+
adapters.each do |adapter|
|
73
|
+
if adapter.run?
|
74
|
+
start(adapter)
|
75
|
+
else
|
76
|
+
stop(adapter)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def start(adapter)
|
82
|
+
stopping.delete(adapter)
|
83
|
+
return if running[adapter]
|
84
|
+
log(adapter.config['name'], "STARTING\n")
|
85
|
+
running[adapter] = start_command(adapter)
|
86
|
+
end
|
87
|
+
|
88
|
+
def start_command(adapter)
|
89
|
+
command = adapter.config['command']
|
90
|
+
Array(command).map do |cmd|
|
91
|
+
rout, wout = IO.pipe
|
92
|
+
pid = spawn(adapter.config['path'], cmd, wout)
|
93
|
+
Thread.new do
|
94
|
+
log(adapter.config['name'], rout.gets) until rout.eof?
|
95
|
+
end
|
96
|
+
[pid, wout]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def spawn(path, cmd, out)
|
101
|
+
Process.spawn(
|
102
|
+
"cd '#{path}' && " + command_template % cmd,
|
103
|
+
pgroup: true,
|
104
|
+
err: out,
|
105
|
+
out: out
|
106
|
+
)
|
107
|
+
end
|
108
|
+
|
109
|
+
def stop(adapter)
|
110
|
+
return unless running[adapter]
|
111
|
+
if should_stop?(adapter)
|
112
|
+
really_stop(adapter)
|
113
|
+
else
|
114
|
+
stopping[adapter] ||= stop_delay
|
115
|
+
stopping[adapter] -= interval
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def should_stop?(adapter)
|
120
|
+
stopping[adapter] && stopping[adapter] <= 0
|
121
|
+
end
|
122
|
+
|
123
|
+
def really_stop(adapter)
|
124
|
+
log(adapter.config['name'], "STOPPING\n")
|
125
|
+
stopping.delete(adapter)
|
126
|
+
return unless running[adapter]
|
127
|
+
running[adapter].each do |(pid, wout)|
|
128
|
+
Process.kill('-TERM', pid)
|
129
|
+
wout.close
|
130
|
+
end
|
131
|
+
running.delete(adapter)
|
132
|
+
end
|
133
|
+
|
134
|
+
def term_all
|
135
|
+
running.values.flatten.each do |(pid)|
|
136
|
+
Process.kill('-TERM', pid)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def log(name, message)
|
141
|
+
color = colors[name]
|
142
|
+
$stdout.write("\e[#{color}m#{name.rjust(label_width)}:\e[0m #{message}")
|
143
|
+
end
|
144
|
+
|
145
|
+
def label_width
|
146
|
+
@label_width ||= apps.keys.map(&:size).max
|
147
|
+
end
|
148
|
+
|
149
|
+
def assign_colors
|
150
|
+
color_values = COLORS.values
|
151
|
+
apps.keys.each_with_object({}) do |app, hash|
|
152
|
+
hash[app] = color_values.shift
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def build_adapters
|
157
|
+
apps.flat_map do |app, config|
|
158
|
+
config.map do |type, worker_config|
|
159
|
+
adapter = adapter_from_type(type)
|
160
|
+
next if adapter.nil?
|
161
|
+
adapter.new(
|
162
|
+
config: worker_config.merge('name' => app, 'path' => config['path']),
|
163
|
+
redis: redis
|
164
|
+
)
|
165
|
+
end
|
166
|
+
end.compact
|
167
|
+
end
|
168
|
+
|
169
|
+
def adapter_from_type(type)
|
170
|
+
case type
|
171
|
+
when 'sidekiq'
|
172
|
+
Adapters::Sidekiq
|
173
|
+
when 'resque'
|
174
|
+
Adapters::Resque
|
175
|
+
when 'path'
|
176
|
+
nil
|
177
|
+
else
|
178
|
+
fail "Unknown type #{type}"
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
metadata
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: honcho
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tim Morgn
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-05-05 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: redis
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
description: Sidekiq- and Resque-aware process manager (alternative to Foreman)
|
28
|
+
email:
|
29
|
+
- tim@timmorgan.org
|
30
|
+
executables:
|
31
|
+
- honcho
|
32
|
+
extensions: []
|
33
|
+
extra_rdoc_files: []
|
34
|
+
files:
|
35
|
+
- bin/honcho
|
36
|
+
- lib/honcho.rb
|
37
|
+
- lib/honcho/adapters.rb
|
38
|
+
- lib/honcho/adapters/base.rb
|
39
|
+
- lib/honcho/adapters/resque.rb
|
40
|
+
- lib/honcho/adapters/sidekiq.rb
|
41
|
+
homepage: https://github.com/seven1m/honcho
|
42
|
+
licenses:
|
43
|
+
- MIT
|
44
|
+
metadata: {}
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options: []
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '0'
|
59
|
+
requirements: []
|
60
|
+
rubyforge_project:
|
61
|
+
rubygems_version: 2.5.1
|
62
|
+
signing_key:
|
63
|
+
specification_version: 4
|
64
|
+
summary: Sidekiq- and Resque-aware process manager (alternative to Foreman)
|
65
|
+
test_files: []
|
66
|
+
has_rdoc:
|