hell 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.
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/bin/hell +1 -1
- data/bin/hell-pusher +20 -0
- data/hell.gemspec +3 -2
- data/lib/hell/app.rb +5 -30
- data/lib/hell/lib/cli.rb +45 -30
- data/lib/hell/lib/helpers.rb +40 -5
- data/lib/hell/public/assets/js/hell.js +9 -7
- data/lib/hell/views/index.erb +1 -0
- metadata +4 -2
data/Rakefile
CHANGED
@@ -23,7 +23,7 @@ Jeweler::Tasks.new do |gem|
|
|
23
23
|
gem.description = %Q{Hell is an open source web interface that exposes a set of capistrano recipes as a json api, for usage within large teams}
|
24
24
|
gem.email = "jose@seatgeek.com"
|
25
25
|
gem.authors = ["Jose Diaz-Gonzalez"]
|
26
|
-
|
26
|
+
gem.executables = ["hell", "hell-pusher"]
|
27
27
|
end
|
28
28
|
Jeweler::RubygemsDotOrgTasks.new
|
29
29
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.1
|
data/bin/hell
CHANGED
@@ -6,7 +6,7 @@ require 'hell/lib/cli'
|
|
6
6
|
ENV["RACK_ENV"] ||= "development"
|
7
7
|
|
8
8
|
unicorn_path = File.expand_path('../../unicorn', __FILE__)
|
9
|
-
options, op = Hell::CLI.
|
9
|
+
options, op = Hell::CLI.unicorn_options(ARGV, unicorn_path)
|
10
10
|
|
11
11
|
unless File.directory? options[:log_path]
|
12
12
|
abort('Missing hell log path %s' % options[:log_path])
|
data/bin/hell-pusher
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/this/will/be/overwritten/or/wrapped/anyways/do/not/worry/ruby
|
2
|
+
# -*- encoding: binary -*-
|
3
|
+
require 'pusher'
|
4
|
+
require 'hell/lib/helpers'
|
5
|
+
|
6
|
+
TASK_ID = ENV.fetch('HELL_TASK_ID', nil)
|
7
|
+
HELL_LOG_PATH = ENV.fetch('HELL_LOG_PATH', File.join(Dir.pwd, 'log'))
|
8
|
+
HELL_SENTINEL_STRINGS = ENV.fetch('HELL_SENTINEL_STRINGS', 'Hellish Task Completed').split(',')
|
9
|
+
abort('Missing hell task id') unless TASK_ID
|
10
|
+
|
11
|
+
unless Hell::Helpers.valid_log(TASK_ID)
|
12
|
+
Hell::Helpers::pusher_error(TASK_ID, "log file '#{TASK_ID}' not found")
|
13
|
+
abort("log file '#{TASK_ID}' not found")
|
14
|
+
end
|
15
|
+
|
16
|
+
Pusher.app_id = ENV.fetch('HELL_PUSHER_APP_ID', nil)
|
17
|
+
Pusher.key = ENV.fetch('HELL_PUSHER_KEY', nil)
|
18
|
+
Pusher.secret = ENV.fetch('HELL_PUSHER_SECRET', nil)
|
19
|
+
|
20
|
+
Hell::Helpers::pusher_success(TASK_ID, "tail -n 1000 -f %s" % File.join(HELL_LOG_PATH, TASK_ID + ".log"))
|
data/hell.gemspec
CHANGED
@@ -5,14 +5,14 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "hell"
|
8
|
-
s.version = "0.1.
|
8
|
+
s.version = "0.1.1"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Jose Diaz-Gonzalez"]
|
12
12
|
s.date = "2013-02-21"
|
13
13
|
s.description = "Hell is an open source web interface that exposes a set of capistrano recipes as a json api, for usage within large teams"
|
14
14
|
s.email = "jose@seatgeek.com"
|
15
|
-
s.executables = ["hell"]
|
15
|
+
s.executables = ["hell", "hell-pusher"]
|
16
16
|
s.extra_rdoc_files = [
|
17
17
|
"LICENSE.txt",
|
18
18
|
"README.markdown"
|
@@ -25,6 +25,7 @@ Gem::Specification.new do |s|
|
|
25
25
|
"Rakefile",
|
26
26
|
"VERSION",
|
27
27
|
"bin/hell",
|
28
|
+
"bin/hell-pusher",
|
28
29
|
"config.ru",
|
29
30
|
"hell.gemspec",
|
30
31
|
"lib/hell.rb",
|
data/lib/hell/app.rb
CHANGED
@@ -14,7 +14,7 @@ require 'hell/lib/cli'
|
|
14
14
|
require 'hell/lib/monkey_patch'
|
15
15
|
require 'hell/lib/helpers'
|
16
16
|
|
17
|
-
options, op = Hell::CLI.option_parser(ARGV
|
17
|
+
options, op = Hell::CLI.option_parser(ARGV)
|
18
18
|
op = nil
|
19
19
|
|
20
20
|
HELL_DIR = Dir.pwd
|
@@ -112,6 +112,8 @@ module Hell
|
|
112
112
|
verbose = "LOGGING=debug" if params[:verbose] == "on"
|
113
113
|
|
114
114
|
task_id = run_in_background!("bundle exec cap -l STDOUT %s %s" % [original_cmd, verbose]) unless tasks.empty?
|
115
|
+
tail_in_background!(task_id) if USE_PUSHER
|
116
|
+
|
115
117
|
response = {}
|
116
118
|
response[:status] = tasks.empty? ? 404 : 200,
|
117
119
|
response[:message] = tasks.empty? ? "Task not found" : "Running task in background",
|
@@ -145,16 +147,6 @@ module Hell
|
|
145
147
|
end
|
146
148
|
end
|
147
149
|
|
148
|
-
def self.init_send
|
149
|
-
if USE_PUSHER
|
150
|
-
alias_method :send_error, :_pusher_error
|
151
|
-
alias_method :send_success, :_pusher_success
|
152
|
-
else
|
153
|
-
alias_method :send_error, :_stream_error
|
154
|
-
alias_method :send_success, :_stream_success
|
155
|
-
end
|
156
|
-
end
|
157
|
-
|
158
150
|
def cap
|
159
151
|
FileUtils.chdir HELL_APP_ROOT do
|
160
152
|
@cap ||= Capistrano::CLI.parse(["-T"])
|
@@ -162,23 +154,7 @@ module Hell
|
|
162
154
|
return @cap
|
163
155
|
end
|
164
156
|
|
165
|
-
def
|
166
|
-
Pusher[task_id].trigger('start', {:data => ''})
|
167
|
-
Pusher[task_id].trigger('message', ws_message("<p>#{message}</p>"))
|
168
|
-
Pusher[task_id].trigger('end', {:data => ''})
|
169
|
-
end
|
170
|
-
|
171
|
-
def _pusher_success(task_id, command, opts = {})
|
172
|
-
opts = {:prepend => false}.merge(opts)
|
173
|
-
Pusher[task_id].trigger('start', {:data => ''})
|
174
|
-
Pusher[task_id].trigger('message', ws_message("<p>#{command}</p>")) unless opts[:prepend] == false
|
175
|
-
IO.popen(command, 'rb') do |io|
|
176
|
-
io.each {|line| push_line(task_id, line, out, io)}
|
177
|
-
end
|
178
|
-
Pusher[task_id].trigger('end', {:data => ''})
|
179
|
-
end
|
180
|
-
|
181
|
-
def _stream_error(task_id, message)
|
157
|
+
def send_error(task_id, message)
|
182
158
|
stream do |out|
|
183
159
|
out << "event: start\ndata:\n\n" unless out.closed?
|
184
160
|
out << "data: " + ws_message("<p>#{message}</p>") + "\n\n" unless out.closed?
|
@@ -187,7 +163,7 @@ module Hell
|
|
187
163
|
end
|
188
164
|
end
|
189
165
|
|
190
|
-
def
|
166
|
+
def send_success(task_id, command, opts = {})
|
191
167
|
opts = {:prepend => false}.merge(opts)
|
192
168
|
stream do |out|
|
193
169
|
out << "event: start\ndata:\n\n" unless out.closed?
|
@@ -199,6 +175,5 @@ module Hell
|
|
199
175
|
end
|
200
176
|
end
|
201
177
|
|
202
|
-
init_send
|
203
178
|
end
|
204
179
|
end
|
data/lib/hell/lib/cli.rb
CHANGED
@@ -4,7 +4,7 @@ require 'optparse'
|
|
4
4
|
|
5
5
|
module Hell
|
6
6
|
class CLI
|
7
|
-
def self.default_options(unicorn_path=nil
|
7
|
+
def self.default_options(unicorn_path=nil)
|
8
8
|
rackup_opts = Unicorn::Configurator::RACKUP || {}
|
9
9
|
rackup_opts[:port] = 4567
|
10
10
|
|
@@ -14,21 +14,35 @@ module Hell
|
|
14
14
|
|
15
15
|
options[:log_path] = ENV.fetch('HELL_LOG_PATH', File.join(Dir.pwd, 'log'))
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
options[:pusher_secret] = ENV.fetch('HELL_PUSHER_SECRET', nil)
|
25
|
-
end
|
17
|
+
options[:app_root] = ENV.fetch('HELL_APP_ROOT', Dir.pwd)
|
18
|
+
options[:base_path] = ENV.fetch('HELL_BASE_PATH', '/')
|
19
|
+
options[:require_env] = !!ENV.fetch('HELL_REQUIRE_ENV', true)
|
20
|
+
options[:sentinel] = ENV.fetch('HELL_SENTINEL_STRINGS', 'Hellish Task Completed').split(',')
|
21
|
+
options[:pusher_app_id] = ENV.fetch('HELL_PUSHER_APP_ID', nil)
|
22
|
+
options[:pusher_key] = ENV.fetch('HELL_PUSHER_KEY', nil)
|
23
|
+
options[:pusher_secret] = ENV.fetch('HELL_PUSHER_SECRET', nil)
|
26
24
|
|
27
25
|
options
|
28
26
|
end
|
29
27
|
|
30
|
-
def self.
|
31
|
-
options = Hell::CLI.
|
28
|
+
def self.unicorn_options(args, unicorn_path=nil)
|
29
|
+
options, op = Hell::CLI.option_parser(args, unicorn_path)
|
30
|
+
|
31
|
+
[
|
32
|
+
:app_root,
|
33
|
+
:base_path,
|
34
|
+
:require_env,
|
35
|
+
:sentinel,
|
36
|
+
:pusher_app_id,
|
37
|
+
:pusher_key,
|
38
|
+
:pusher_secret,
|
39
|
+
].each {|key| options.delete(key)}
|
40
|
+
|
41
|
+
[options, op]
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.option_parser(args, unicorn_path=nil)
|
45
|
+
options = Hell::CLI.default_options(unicorn_path)
|
32
46
|
|
33
47
|
op = OptionParser.new("", 24, ' ') do |opts|
|
34
48
|
cmd = File.basename($0)
|
@@ -114,7 +128,8 @@ module Hell
|
|
114
128
|
# config files and make things unnecessarily complicated with multiple
|
115
129
|
# places to look for a config option.
|
116
130
|
|
117
|
-
opts.separator "Common options
|
131
|
+
opts.separator "Common options"
|
132
|
+
opts.separator "Note: will not work under unicorn, use environment variables instead"
|
118
133
|
|
119
134
|
opts.on_tail("-h", "--help", "Show this message") do
|
120
135
|
puts opts.to_s.gsub(/^.*DEPRECATED.*$/s, '')
|
@@ -126,39 +141,39 @@ module Hell
|
|
126
141
|
exit
|
127
142
|
end
|
128
143
|
|
129
|
-
opts.on('-a', '--app-root APP_ROOT', 'directory from which capistrano should run') do |
|
130
|
-
options[:app_root] =
|
144
|
+
opts.on('-a', '--app-root APP_ROOT', 'directory from which capistrano should run') do |app_root|
|
145
|
+
options[:app_root] = app_root if app_root
|
131
146
|
end
|
132
147
|
|
133
|
-
opts.on('-b', '--base-path BASE_PATH', 'base directory path to use in the web ui') do |
|
134
|
-
options[:base_path] =
|
148
|
+
opts.on('-b', '--base-path BASE_PATH', 'base directory path to use in the web ui') do |base_path|
|
149
|
+
options[:base_path] = base_path if base_path
|
135
150
|
end
|
136
151
|
|
137
|
-
opts.on('-L', '--log-path LOG_PATH', 'directory path to hell logs') do |
|
138
|
-
options[:log_path] =
|
152
|
+
opts.on('-L', '--log-path LOG_PATH', 'directory path to hell logs') do |log_path|
|
153
|
+
options[:log_path] = log_path if log_path
|
139
154
|
end
|
140
155
|
|
141
|
-
opts.on('-R', '--require-env REQUIRE_ENV', 'whether or not to require specifying an environment') do |
|
142
|
-
options[:require_env] = !!
|
156
|
+
opts.on('-R', '--require-env REQUIRE_ENV', 'whether or not to require specifying an environment') do |require_env|
|
157
|
+
options[:require_env] = !!require_env if require_env
|
143
158
|
end
|
144
159
|
|
145
|
-
opts.on('-S', '--sentinel SENTINAL_PHRASE', 'sentinel phrase used to denote the end of a task run') do |
|
146
|
-
options[:sentinel] =
|
160
|
+
opts.on('-S', '--sentinel SENTINAL_PHRASE', 'sentinel phrase used to denote the end of a task run') do |sentinel|
|
161
|
+
options[:sentinel] = sentinel.split(',') if sentinel
|
147
162
|
end
|
148
163
|
|
149
|
-
opts.on('--pusher-app-id PUSHER_APP_ID', 'pusher app id') do |
|
150
|
-
options[:pusher_app_id] =
|
164
|
+
opts.on('--pusher-app-id PUSHER_APP_ID', 'pusher app id') do |pusher_app_id|
|
165
|
+
options[:pusher_app_id] = pusher_app_id if pusher_app_id
|
151
166
|
end
|
152
167
|
|
153
|
-
opts.on('--pusher-key PUSHER_KEY', 'pusher key') do |
|
154
|
-
options[:pusher_key] =
|
168
|
+
opts.on('--pusher-key PUSHER_KEY', 'pusher key') do |pusher_key|
|
169
|
+
options[:pusher_key] = pusher_key if pusher_key
|
155
170
|
end
|
156
171
|
|
157
|
-
opts.on('--pusher-secret PUSHER_SECRET', 'pusher secret') do |
|
158
|
-
options[:pusher_secret] =
|
172
|
+
opts.on('--pusher-secret PUSHER_SECRET', 'pusher secret') do |pusher_secret|
|
173
|
+
options[:pusher_secret] = pusher_secret if pusher_secret
|
159
174
|
end
|
160
175
|
|
161
|
-
opts.parse!
|
176
|
+
opts.parse! args
|
162
177
|
end
|
163
178
|
|
164
179
|
[options, op]
|
data/lib/hell/lib/helpers.rb
CHANGED
@@ -5,6 +5,7 @@ module Hell
|
|
5
5
|
class TailDone < StandardError; end
|
6
6
|
|
7
7
|
module Helpers
|
8
|
+
|
8
9
|
def escape_to_html(data)
|
9
10
|
{
|
10
11
|
1 => :nothing,
|
@@ -44,11 +45,11 @@ module Hell
|
|
44
45
|
end
|
45
46
|
|
46
47
|
def ansi_escape(message)
|
47
|
-
escape_to_html(utf8_dammit(message))
|
48
|
+
Hell::Helpers::escape_to_html(Hell::Helpers::utf8_dammit(message))
|
48
49
|
end
|
49
50
|
|
50
51
|
def ws_message(message)
|
51
|
-
|
52
|
+
{:message => Hell::Helpers::ansi_escape(message)}
|
52
53
|
end
|
53
54
|
|
54
55
|
def stream_line(task_id, line, out, io)
|
@@ -60,11 +61,11 @@ module Hell
|
|
60
61
|
end
|
61
62
|
end
|
62
63
|
|
63
|
-
def push_line(task_id, line,
|
64
|
+
def push_line(task_id, line, io)
|
64
65
|
begin
|
65
|
-
Pusher[task_id].trigger('message', ws_message(line))
|
66
|
+
Pusher[task_id].trigger('message', MultiJson.dump(Hell::Helpers::ws_message(line)))
|
66
67
|
raise TailDone if HELL_SENTINEL_STRINGS.any? { |w| line =~ /#{w}/ }
|
67
|
-
rescue
|
68
|
+
rescue StandardError => e
|
68
69
|
Process.kill("KILL", io.pid)
|
69
70
|
end
|
70
71
|
end
|
@@ -98,6 +99,11 @@ module Hell
|
|
98
99
|
log_file
|
99
100
|
end
|
100
101
|
|
102
|
+
def tail_in_background!(task_id)
|
103
|
+
cmd = "cd #{HELL_LOG_PATH} && HELL_TASK_ID='#{task_id}' HELL_SENTINEL_STRINGS='#{HELL_SENTINEL_STRINGS.join(',')}' HELL_LOG_PATH='#{HELL_LOG_PATH}' bundle exec hell-pusher"
|
104
|
+
system("sh -c \"#{cmd}\" &")
|
105
|
+
end
|
106
|
+
|
101
107
|
def verify_task(cap, name)
|
102
108
|
original_cmd = name.gsub('+', ' ').gsub!(/\s+/, ' ').strip
|
103
109
|
cmd = original_cmd.split(' ')
|
@@ -134,5 +140,34 @@ module Hell
|
|
134
140
|
def json_encode(data)
|
135
141
|
MultiJson.dump(data)
|
136
142
|
end
|
143
|
+
|
144
|
+
def pusher_error(task_id, message)
|
145
|
+
Pusher[task_id].trigger('start', MultiJson.dump(Hell::Helpers::ws_message("<p>start</p>")))
|
146
|
+
Pusher[task_id].trigger('message', MultiJson.dump(Hell::Helpers::ws_message("<p>#{message}</p>")))
|
147
|
+
Pusher[task_id].trigger('end', MultiJson.dump(Hell::Helpers::ws_message("<p>end</p>")))
|
148
|
+
end
|
149
|
+
|
150
|
+
def pusher_success(task_id, command, opts = {})
|
151
|
+
out = nil
|
152
|
+
opts = {:prepend => false}.merge(opts)
|
153
|
+
Pusher[task_id].trigger('start', MultiJson.dump(Hell::Helpers::ws_message("<p>start</p>")))
|
154
|
+
Pusher[task_id].trigger('message', MultiJson.dump(Hell::Helpers::ws_message("<p>#{command}</p>"))) unless opts[:prepend] == false
|
155
|
+
IO.popen(command, 'rb') do |io|
|
156
|
+
io.each do |line|
|
157
|
+
Hell::Helpers::push_line(task_id, line, io)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
Pusher[task_id].trigger('end', MultiJson.dump(Hell::Helpers::ws_message("<p>end</p>")))
|
161
|
+
end
|
162
|
+
|
163
|
+
module_function :pusher_error
|
164
|
+
module_function :pusher_success
|
165
|
+
module_function :push_line
|
166
|
+
module_function :valid_log
|
167
|
+
module_function :ws_message
|
168
|
+
module_function :utf8_dammit
|
169
|
+
module_function :ansi_escape
|
170
|
+
module_function :escape_to_html
|
171
|
+
|
137
172
|
end
|
138
173
|
end
|
@@ -139,6 +139,8 @@ $(function() {
|
|
139
139
|
_executePusher: function (task_id, name) {
|
140
140
|
var that = this;
|
141
141
|
|
142
|
+
HellApp.setupPusher();
|
143
|
+
|
142
144
|
// Create a new channel
|
143
145
|
var channel = HellApp.pusher.subscribe(task_id);
|
144
146
|
|
@@ -152,13 +154,12 @@ $(function() {
|
|
152
154
|
channel.bind('end', function(data) {
|
153
155
|
HellApp.set_flash("Received the last response " + name, "info");
|
154
156
|
that.setStatus("finished");
|
155
|
-
pusher.unsubscribe(task_id);
|
157
|
+
HellApp.pusher.unsubscribe(task_id);
|
156
158
|
});
|
157
159
|
|
158
160
|
// Append the messages to the .task-output div
|
159
161
|
channel.bind('message', function(data) {
|
160
|
-
|
161
|
-
$('.task-output').append(message.message.replace(/~+$/, ''));
|
162
|
+
$('.task-output').append(data.message.replace(/~+$/, ''));
|
162
163
|
$(".task-output").animate({scrollTop: $(".task-output").prop("scrollHeight")}, 1);
|
163
164
|
});
|
164
165
|
},
|
@@ -454,14 +455,15 @@ $(function() {
|
|
454
455
|
src = null;
|
455
456
|
}
|
456
457
|
},
|
458
|
+
setupPusher: function() {
|
459
|
+
if (HellApp.use_pusher) {
|
460
|
+
HellApp.pusher = HellApp.pusher || new Pusher(HellApp.pusher_key);
|
461
|
+
}
|
462
|
+
},
|
457
463
|
collection: new TaskCollectionView(),
|
458
464
|
form: new TaskFormView()
|
459
465
|
});
|
460
466
|
|
461
|
-
if (HellApp.use_pusher) {
|
462
|
-
HellApp.pusher = Pusher(HellApp.pusher_key);
|
463
|
-
}
|
464
|
-
|
465
467
|
// HACK: This should be moved into a Backbone View or Router
|
466
468
|
|
467
469
|
// Function to activate the tab
|
data/lib/hell/views/index.erb
CHANGED
@@ -125,6 +125,7 @@
|
|
125
125
|
HellApp.pusher_secret = <%= json_encode(@pusher_secret) %>;
|
126
126
|
</script>
|
127
127
|
|
128
|
+
<script src="http://js.pusher.com/1.12/pusher.min.js"></script>
|
128
129
|
<%= js :jquery %>
|
129
130
|
<script src="<%= @www_base_dir %>assets/js/bootstrap.js"></script>
|
130
131
|
<script src="<%= @www_base_dir %>assets/js/underscore.js"></script>
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hell
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -176,6 +176,7 @@ description: Hell is an open source web interface that exposes a set of capistra
|
|
176
176
|
email: jose@seatgeek.com
|
177
177
|
executables:
|
178
178
|
- hell
|
179
|
+
- hell-pusher
|
179
180
|
extensions: []
|
180
181
|
extra_rdoc_files:
|
181
182
|
- LICENSE.txt
|
@@ -188,6 +189,7 @@ files:
|
|
188
189
|
- Rakefile
|
189
190
|
- VERSION
|
190
191
|
- bin/hell
|
192
|
+
- bin/hell-pusher
|
191
193
|
- config.ru
|
192
194
|
- hell.gemspec
|
193
195
|
- lib/hell.rb
|
@@ -236,7 +238,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
236
238
|
version: '0'
|
237
239
|
segments:
|
238
240
|
- 0
|
239
|
-
hash: -
|
241
|
+
hash: -2842994347166960270
|
240
242
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
241
243
|
none: false
|
242
244
|
requirements:
|