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