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 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,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/honcho'
4
+
5
+ Honcho.new.run
@@ -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
@@ -0,0 +1,3 @@
1
+ require_relative './adapters/base'
2
+ require_relative './adapters/resque'
3
+ require_relative './adapters/sidekiq'
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: