hell 0.0.1 → 0.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.
- data/Gemfile +5 -2
- data/README.markdown +0 -1
- data/VERSION +1 -1
- data/bin/hell +28 -10
- data/config.ru +7 -0
- data/hell.gemspec +107 -0
- data/lib/hell.rb +4 -0
- data/lib/hell/app.rb +108 -32
- data/lib/hell/lib/capistrano/cli.rb +57 -0
- data/lib/hell/lib/capistrano/configuration.rb +14 -0
- data/lib/hell/lib/capistrano/task_definition.rb +18 -0
- data/lib/hell/lib/cli.rb +167 -0
- data/lib/hell/lib/helpers.rb +32 -13
- data/lib/hell/lib/monkey_patch.rb +4 -70
- data/lib/hell/public/assets/js/bootstrap.growl.js +85 -1
- data/lib/hell/public/assets/js/bootstrap.js +7 -2025
- data/lib/hell/public/assets/js/hashchange.js +300 -0
- data/lib/hell/public/assets/js/hell.js +63 -21
- data/lib/hell/public/assets/js/jquery.js +9555 -0
- data/lib/hell/public/assets/js/{underscore.min.js → underscore.js} +1 -1
- data/lib/hell/views/index.erb +14 -25
- data/unicorn +2 -0
- metadata +63 -12
- data/lib/hell/config.ru +0 -4
- data/lib/hell/hell.rb +0 -0
- data/lib/hell/public/assets/js/backbone.min.js +0 -38
- data/lib/hell/public/assets/js/bootstrap.min.js +0 -6
- data/lib/hell/public/assets/js/hashchange.min.js +0 -9
- data/lib/hell/public/assets/js/jquery.min.js +0 -4
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'capistrano/configuration/loading'
|
2
|
+
|
3
|
+
module Capistrano
|
4
|
+
class Configuration
|
5
|
+
module Loading
|
6
|
+
alias_method :original_initialize_with_loading, :initialize_with_loading
|
7
|
+
def initialize_with_loading(*args) #:nodoc:
|
8
|
+
orig = original_initialize_with_loading(*args)
|
9
|
+
@load_paths.unshift(HELL_APP_ROOT)
|
10
|
+
return orig
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'capistrano/task_definition'
|
2
|
+
|
3
|
+
module Capistrano
|
4
|
+
class TaskDefinition
|
5
|
+
|
6
|
+
def to_hash
|
7
|
+
# Roles should always be a hash, to ease developer frustration
|
8
|
+
@options[:roles] = Array(@options[:roles])
|
9
|
+
{
|
10
|
+
:name => name,
|
11
|
+
:fully_qualified_name => fully_qualified_name,
|
12
|
+
:description => description == brief_description ? false : description,
|
13
|
+
:brief_description => brief_description,
|
14
|
+
:options => @options,
|
15
|
+
}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/hell/lib/cli.rb
ADDED
@@ -0,0 +1,167 @@
|
|
1
|
+
require 'unicorn/launcher'
|
2
|
+
require 'unicorn/configurator'
|
3
|
+
require 'optparse'
|
4
|
+
|
5
|
+
module Hell
|
6
|
+
class CLI
|
7
|
+
def self.default_options(unicorn_path=nil, sinatra_opts=false)
|
8
|
+
rackup_opts = Unicorn::Configurator::RACKUP || {}
|
9
|
+
rackup_opts[:port] = 4567
|
10
|
+
|
11
|
+
options = rackup_opts[:options] || {}
|
12
|
+
options[:config_file] = unicorn_path unless unicorn_path.nil?
|
13
|
+
options[:listeners] = ['0.0.0.0:4567']
|
14
|
+
|
15
|
+
options[:log_path] = ENV.fetch('HELL_LOG_PATH', File.join(Dir.pwd, 'log'))
|
16
|
+
|
17
|
+
if sinatra_opts
|
18
|
+
options[:app_root] = ENV.fetch('HELL_APP_ROOT', Dir.pwd)
|
19
|
+
options[:base_path] = ENV.fetch('HELL_BASE_PATH', '/')
|
20
|
+
options[:require_env] = !!ENV.fetch('HELL_REQUIRE_ENV', true)
|
21
|
+
options[:sentinel] = ENV.fetch('HELL_SENTINEL_STRINGS', 'Hellish Task Completed').split(',')
|
22
|
+
options[:pusher_app_id] = ENV.fetch('HELL_PUSHER_APP_ID', nil)
|
23
|
+
options[:pusher_key] = ENV.fetch('HELL_PUSHER_KEY', nil)
|
24
|
+
options[:pusher_secret] = ENV.fetch('HELL_PUSHER_SECRET', nil)
|
25
|
+
end
|
26
|
+
|
27
|
+
options
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.option_parser(args, unicorn_path=nil, sinatra_opts=false)
|
31
|
+
options = Hell::CLI.default_options(unicorn_path, sinatra_opts)
|
32
|
+
|
33
|
+
op = OptionParser.new("", 24, ' ') do |opts|
|
34
|
+
cmd = File.basename($0)
|
35
|
+
opts.banner = "Usage: #{cmd} " \
|
36
|
+
"[ruby options] [#{cmd} options] [rackup config file]"
|
37
|
+
opts.separator "Ruby options:"
|
38
|
+
|
39
|
+
lineno = 1
|
40
|
+
opts.on("-e", "--eval LINE", "evaluate a LINE of code") do |line|
|
41
|
+
eval line, TOPLEVEL_BINDING, "-e", lineno
|
42
|
+
lineno += 1
|
43
|
+
end
|
44
|
+
|
45
|
+
opts.on("-d", "--debug", "set debugging flags (set $DEBUG to true)") do
|
46
|
+
$DEBUG = true
|
47
|
+
end
|
48
|
+
|
49
|
+
opts.on("-w", "--warn", "turn warnings on for your script") do
|
50
|
+
$-w = true
|
51
|
+
end
|
52
|
+
|
53
|
+
opts.on("-I", "--include PATH",
|
54
|
+
"specify $LOAD_PATH (may be used more than once)") do |path|
|
55
|
+
$LOAD_PATH.unshift(*path.split(/:/))
|
56
|
+
end
|
57
|
+
|
58
|
+
opts.on("-r", "--require LIBRARY",
|
59
|
+
"require the library, before executing your script") do |library|
|
60
|
+
require library
|
61
|
+
end
|
62
|
+
|
63
|
+
opts.separator "Unicorn options:"
|
64
|
+
|
65
|
+
# some of these switches exist for rackup command-line compatibility,
|
66
|
+
|
67
|
+
opts.on("-o", "--host HOST",
|
68
|
+
"listen on HOST (default: 0.0.0.0)") do |h|
|
69
|
+
rackup_opts[:host] = h || '0.0.0.0'
|
70
|
+
rackup_opts[:set_listener] = true
|
71
|
+
end
|
72
|
+
|
73
|
+
opts.on("-p", "--port PORT",
|
74
|
+
"use PORT (default: 4567)") do |p|
|
75
|
+
rackup_opts[:port] = p || 4567
|
76
|
+
rackup_opts[:port] = rackup_opts[:port].to_i
|
77
|
+
rackup_opts[:set_listener] = true
|
78
|
+
end
|
79
|
+
|
80
|
+
opts.on("-E", "--env RACK_ENV",
|
81
|
+
"use RACK_ENV for defaults (default: development)") do |e|
|
82
|
+
ENV["RACK_ENV"] = e
|
83
|
+
end
|
84
|
+
|
85
|
+
opts.on("-D", "--daemonize", "run daemonized in the background") do |d|
|
86
|
+
rackup_opts[:daemonize] = !!d
|
87
|
+
end
|
88
|
+
|
89
|
+
opts.on("-P", "--pid FILE", "DEPRECATED") do |f|
|
90
|
+
warn %q{Use of --pid/-P is strongly discouraged}
|
91
|
+
warn %q{Use the 'pid' directive in the Unicorn config file instead}
|
92
|
+
options[:pid] = f
|
93
|
+
end
|
94
|
+
|
95
|
+
opts.on("-s", "--server SERVER",
|
96
|
+
"this flag only exists for compatibility") do |s|
|
97
|
+
warn "-s/--server only exists for compatibility with rackup"
|
98
|
+
end
|
99
|
+
|
100
|
+
# Unicorn-specific stuff
|
101
|
+
opts.on("-l", "--listen {HOST:PORT|PATH}",
|
102
|
+
"listen on HOST:PORT or PATH",
|
103
|
+
"this may be specified multiple times",
|
104
|
+
"(default: 0.0.0.0:4567)") do |address|
|
105
|
+
options[:listeners] << address || '0.0.0.0:4567'
|
106
|
+
end
|
107
|
+
|
108
|
+
opts.on("-c", "--config-file FILE", "Unicorn-specific config file") do |f|
|
109
|
+
options[:config_file] = unicorn_path
|
110
|
+
end
|
111
|
+
|
112
|
+
# I'm avoiding Unicorn-specific config options on the command-line.
|
113
|
+
# IMNSHO, config options on the command-line are redundant given
|
114
|
+
# config files and make things unnecessarily complicated with multiple
|
115
|
+
# places to look for a config option.
|
116
|
+
|
117
|
+
opts.separator "Common options:"
|
118
|
+
|
119
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
120
|
+
puts opts.to_s.gsub(/^.*DEPRECATED.*$/s, '')
|
121
|
+
exit
|
122
|
+
end
|
123
|
+
|
124
|
+
opts.on_tail("-v", "--version", "Show version") do
|
125
|
+
puts "#{cmd} v#{Unicorn::Const::UNICORN_VERSION}"
|
126
|
+
exit
|
127
|
+
end
|
128
|
+
|
129
|
+
opts.on('-a', '--app-root APP_ROOT', 'directory from which capistrano should run') do |opt|
|
130
|
+
options[:app_root] = opt if opt && sinatra_opts
|
131
|
+
end
|
132
|
+
|
133
|
+
opts.on('-b', '--base-path BASE_PATH', 'base directory path to use in the web ui') do |opt|
|
134
|
+
options[:base_path] = opt if opt && sinatra_opts
|
135
|
+
end
|
136
|
+
|
137
|
+
opts.on('-L', '--log-path LOG_PATH', 'directory path to hell logs') do |opt|
|
138
|
+
options[:log_path] = opt if opt
|
139
|
+
end
|
140
|
+
|
141
|
+
opts.on('-R', '--require-env REQUIRE_ENV', 'whether or not to require specifying an environment') do |opt|
|
142
|
+
options[:require_env] = !!opt if opt && sinatra_opts
|
143
|
+
end
|
144
|
+
|
145
|
+
opts.on('-S', '--sentinel SENTINAL_PHRASE', 'sentinel phrase used to denote the end of a task run') do |opt|
|
146
|
+
options[:sentinel] = opt.split(',') if opt && sinatra_opts
|
147
|
+
end
|
148
|
+
|
149
|
+
opts.on('--pusher-app-id PUSHER_APP_ID', 'pusher app id') do |opt|
|
150
|
+
options[:pusher_app_id] = opt if opt && sinatra_opts
|
151
|
+
end
|
152
|
+
|
153
|
+
opts.on('--pusher-key PUSHER_KEY', 'pusher key') do |opt|
|
154
|
+
options[:pusher_key] = opt if opt && sinatra_opts
|
155
|
+
end
|
156
|
+
|
157
|
+
opts.on('--pusher-secret PUSHER_SECRET', 'pusher secret') do |opt|
|
158
|
+
options[:pusher_secret] = opt if opt && sinatra_opts
|
159
|
+
end
|
160
|
+
|
161
|
+
opts.parse! ARGV
|
162
|
+
end
|
163
|
+
|
164
|
+
[options, op]
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
data/lib/hell/lib/helpers.rb
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
require 'multi_json'
|
2
|
+
require 'pusher'
|
3
|
+
|
1
4
|
module Hell
|
2
5
|
class TailDone < StandardError; end
|
3
6
|
|
@@ -45,13 +48,22 @@ module Hell
|
|
45
48
|
end
|
46
49
|
|
47
50
|
def ws_message(message)
|
48
|
-
message = {:message => ansi_escape(message)}.to_json
|
51
|
+
message = {:message => ansi_escape(message)}.to_json
|
49
52
|
end
|
50
53
|
|
51
|
-
def
|
54
|
+
def stream_line(task_id, line, out, io)
|
52
55
|
begin
|
53
|
-
out << "data: " + ws_message(line) unless out.closed?
|
54
|
-
raise TailDone if
|
56
|
+
out << "data: " + ws_message(line) + "\n\n" unless out.closed?
|
57
|
+
raise TailDone if HELL_SENTINEL_STRINGS.any? { |w| line =~ /#{w}/ }
|
58
|
+
rescue
|
59
|
+
Process.kill("KILL", io.pid)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def push_line(task_id, line, out, io)
|
64
|
+
begin
|
65
|
+
Pusher[task_id].trigger('message', ws_message(line))
|
66
|
+
raise TailDone if HELL_SENTINEL_STRINGS.any? { |w| line =~ /#{w}/ }
|
55
67
|
rescue
|
56
68
|
Process.kill("KILL", io.pid)
|
57
69
|
end
|
@@ -62,14 +74,17 @@ module Hell
|
|
62
74
|
out.close
|
63
75
|
end
|
64
76
|
|
77
|
+
def random_id
|
78
|
+
Time.now.to_i.to_s + '.' + SecureRandom.hex(2)
|
79
|
+
end
|
80
|
+
|
65
81
|
def run_in_background!(background_cmd)
|
66
|
-
log_file =
|
82
|
+
log_file = random_id
|
67
83
|
cmd = [
|
68
|
-
"cd #{
|
69
|
-
"
|
70
|
-
"#{
|
71
|
-
|
72
|
-
].join(" && ")
|
84
|
+
"cd #{HELL_APP_ROOT} && echo '#{background_cmd}' >> #{HELL_LOG_PATH}/#{log_file}.log 2>&1",
|
85
|
+
"cd #{HELL_APP_ROOT} && #{background_cmd} >> #{HELL_LOG_PATH}/#{log_file}.log 2>&1",
|
86
|
+
"cd #{HELL_APP_ROOT} && echo 'Hellish Task Completed' >> #{HELL_LOG_PATH}/#{log_file}.log 2>&1",
|
87
|
+
].join(" ; ")
|
73
88
|
system("sh -c \"#{cmd}\" &")
|
74
89
|
|
75
90
|
# Wait up to three seconds in case of server load
|
@@ -84,12 +99,12 @@ module Hell
|
|
84
99
|
end
|
85
100
|
|
86
101
|
def verify_task(cap, name)
|
87
|
-
original_cmd = name.gsub('+', ' ')
|
102
|
+
original_cmd = name.gsub('+', ' ').gsub!(/\s+/, ' ').strip
|
88
103
|
cmd = original_cmd.split(' ')
|
89
|
-
cmd.shift if
|
104
|
+
cmd.shift if cap.environments.include?(cmd.first)
|
90
105
|
cmd = cmd.join(' ')
|
91
106
|
|
92
|
-
tasks = cap.
|
107
|
+
tasks = cap.tasks(cmd, {:exact => true})
|
93
108
|
return tasks, original_cmd
|
94
109
|
end
|
95
110
|
|
@@ -115,5 +130,9 @@ module Hell
|
|
115
130
|
s
|
116
131
|
end
|
117
132
|
end
|
133
|
+
|
134
|
+
def json_encode(data)
|
135
|
+
MultiJson.dump(data)
|
136
|
+
end
|
118
137
|
end
|
119
138
|
end
|
@@ -1,64 +1,9 @@
|
|
1
|
-
require '
|
2
|
-
require 'capistrano'
|
1
|
+
require 'hell/lib/capistrano/configuration'
|
2
|
+
require 'hell/lib/capistrano/cli'
|
3
|
+
require 'hell/lib/capistrano/task_definition'
|
4
|
+
|
3
5
|
require 'capistrano/cli'
|
4
6
|
require 'capistrano/cli/help'
|
5
|
-
require 'capistrano/task_definition'
|
6
|
-
|
7
|
-
module Capistrano
|
8
|
-
class TaskDefinition
|
9
|
-
def to_hash
|
10
|
-
{
|
11
|
-
:name => name,
|
12
|
-
:fully_qualified_name => fully_qualified_name,
|
13
|
-
:description => description == brief_description ? false : description,
|
14
|
-
:brief_description => brief_description,
|
15
|
-
}
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
class CLI
|
20
|
-
module JsonTaskList
|
21
|
-
def task_before
|
22
|
-
config = instantiate_configuration(options)
|
23
|
-
config.debug = options[:debug]
|
24
|
-
config.dry_run = options[:dry_run]
|
25
|
-
config.preserve_roles = options[:preserve_roles]
|
26
|
-
config.logger.level = options[:verbose]
|
27
|
-
|
28
|
-
set_pre_vars(config)
|
29
|
-
load_recipes(config)
|
30
|
-
|
31
|
-
config.trigger(:load)
|
32
|
-
[config, options]
|
33
|
-
end
|
34
|
-
|
35
|
-
def task_after(config)
|
36
|
-
config.trigger(:exit)
|
37
|
-
config
|
38
|
-
end
|
39
|
-
|
40
|
-
def task_index(pattern = nil, opts = {})
|
41
|
-
config, options = task_before
|
42
|
-
|
43
|
-
tasks = config.task_list(:all)
|
44
|
-
if opts.fetch(:exact, false) && pattern.is_a?(String)
|
45
|
-
tasks.select! {|t| t.fully_qualified_name == pattern}
|
46
|
-
elsif pattern.is_a?(String)
|
47
|
-
tasks.select! {|t| t.fully_qualified_name =~ /#{pattern}/}
|
48
|
-
end
|
49
|
-
|
50
|
-
tasks.reject! {|t| BLACKLIST.include?(t.fully_qualified_name)}
|
51
|
-
tasks.reject! {|t| ENVIRONMENTS.include?(t.fully_qualified_name)}
|
52
|
-
tasks.reject! {|t| t.service.include?(opts.fetch(:service))} if opts.fetch(:service, false)
|
53
|
-
|
54
|
-
tasks = Hash[tasks.map {|task| [task.fully_qualified_name, task.to_hash]}]
|
55
|
-
task_after(config)
|
56
|
-
|
57
|
-
tasks
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
7
|
|
63
8
|
module Capistrano
|
64
9
|
class CLI
|
@@ -66,14 +11,3 @@ module Capistrano
|
|
66
11
|
include Help
|
67
12
|
end
|
68
13
|
end
|
69
|
-
|
70
|
-
module Capistrano
|
71
|
-
class TaskDefinition
|
72
|
-
attr_reader :service
|
73
|
-
|
74
|
-
def service
|
75
|
-
@service ||= Array(@options.delete(:service))
|
76
|
-
end
|
77
|
-
|
78
|
-
end
|
79
|
-
end
|
@@ -1,2 +1,86 @@
|
|
1
1
|
/* https://github.com/ifightcrime/bootstrap-growl */
|
2
|
-
|
2
|
+
|
3
|
+
|
4
|
+
(function($) {
|
5
|
+
$.bootstrapGrowl = function(message, options) {
|
6
|
+
|
7
|
+
var options = $.extend({}, $.bootstrapGrowl.default_options, options);
|
8
|
+
|
9
|
+
var $alert = $('<div>');
|
10
|
+
|
11
|
+
$alert.attr('class', 'bootstrap-growl alert');
|
12
|
+
|
13
|
+
if (options.type) {
|
14
|
+
$alert.addClass('alert-' + options.type);
|
15
|
+
}
|
16
|
+
|
17
|
+
if (options.allow_dismiss) {
|
18
|
+
$alert.append('<a class="close" data-dismiss="alert" href="#">×</a>');
|
19
|
+
}
|
20
|
+
|
21
|
+
$alert.append(message);
|
22
|
+
|
23
|
+
// Prevent BC breaks
|
24
|
+
if (options.top_offset) {
|
25
|
+
options.offset = {from: 'top', amount: options.top_offset};
|
26
|
+
}
|
27
|
+
var current = $('.bootstrap-growl', options.ele);
|
28
|
+
|
29
|
+
// calculate any 'stack-up'
|
30
|
+
var offsetAmount = options.offset.amount;
|
31
|
+
$.each(current, function() {
|
32
|
+
offsetAmount = Math.max(offsetAmount, parseInt($(this).css(options.offset.from), 10) + $(this).outerHeight() + options.stackup_spacing);
|
33
|
+
});
|
34
|
+
|
35
|
+
css = {
|
36
|
+
'position': 'absolute',
|
37
|
+
'margin': 0,
|
38
|
+
'z-index': '9999',
|
39
|
+
'display': 'none'
|
40
|
+
};
|
41
|
+
css[options.offset.from] = offsetAmount + 'px';
|
42
|
+
$alert.css(css);
|
43
|
+
|
44
|
+
if (options.width !== 'auto') {
|
45
|
+
$alert.css('width', options.width + 'px');
|
46
|
+
}
|
47
|
+
|
48
|
+
// have to append before we can use outerWidth()
|
49
|
+
$(options.ele).append($alert);
|
50
|
+
|
51
|
+
switch(options.align) {
|
52
|
+
case 'center':
|
53
|
+
$alert.css({
|
54
|
+
'left': '50%',
|
55
|
+
'margin-left': '-' + ($alert.outerWidth() / 2) + 'px'
|
56
|
+
});
|
57
|
+
break;
|
58
|
+
case 'left':
|
59
|
+
$alert.css('left', '20px');
|
60
|
+
break;
|
61
|
+
default:
|
62
|
+
$alert.css('right', '20px');
|
63
|
+
}
|
64
|
+
|
65
|
+
$alert.fadeIn();
|
66
|
+
// Only remove after delay if delay is more than 0
|
67
|
+
if(options.delay >= 0){
|
68
|
+
$alert.delay(options.delay).fadeOut('slow', function() {
|
69
|
+
$(this).remove();
|
70
|
+
});
|
71
|
+
}
|
72
|
+
|
73
|
+
};
|
74
|
+
|
75
|
+
$.bootstrapGrowl.default_options = {
|
76
|
+
ele: 'body',
|
77
|
+
type: null,
|
78
|
+
offset: {from: 'top', amount: 20},
|
79
|
+
align: 'right', // (left, right, or center)
|
80
|
+
width: 250,
|
81
|
+
delay: 4000,
|
82
|
+
allow_dismiss: true,
|
83
|
+
stackup_spacing: 10
|
84
|
+
};
|
85
|
+
|
86
|
+
})(jQuery);
|