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
data/Gemfile
CHANGED
data/README.markdown
CHANGED
@@ -64,7 +64,6 @@ Note that the current response is subject to change, and as such is not document
|
|
64
64
|
The following environment variables are available for your use:
|
65
65
|
|
66
66
|
- `HELL_APP_ROOT`: Path from which capistrano should execute. Defaults to `Dir.pwd`.
|
67
|
-
- `HELL_ENVIRONMENTS`: Comma-separated list of environments, Default: `production,staging`.
|
68
67
|
- `HELL_REQUIRE_ENV`: Whether or not to require specifying an environment. Default: `1`.
|
69
68
|
- `HELL_LOG_PATH`: Path to which logs should be written to. Defaults to `Dir.pwd + '/log'`.
|
70
69
|
- `HELL_BASE_DIR`: Base directory to use in web ui. Useful for subdirectories. Defaults to `/`.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0
|
1
|
+
0.1.0
|
data/bin/hell
CHANGED
@@ -1,12 +1,30 @@
|
|
1
|
-
#!/
|
2
|
-
#
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
1
|
+
#!/this/will/be/overwritten/or/wrapped/anyways/do/not/worry/ruby
|
2
|
+
# -*- encoding: binary -*-
|
3
|
+
require 'unicorn/launcher'
|
4
|
+
require 'hell/lib/cli'
|
5
|
+
|
6
|
+
ENV["RACK_ENV"] ||= "development"
|
7
|
+
|
8
|
+
unicorn_path = File.expand_path('../../unicorn', __FILE__)
|
9
|
+
options, op = Hell::CLI.option_parser(ARGV, unicorn_path)
|
10
|
+
|
11
|
+
unless File.directory? options[:log_path]
|
12
|
+
abort('Missing hell log path %s' % options[:log_path])
|
13
|
+
end
|
14
|
+
|
15
|
+
options.delete(:log_path)
|
16
|
+
|
17
|
+
app = Unicorn.builder(File.expand_path('../../config.ru', __FILE__), op)
|
18
|
+
op = nil
|
19
|
+
|
20
|
+
if $DEBUG
|
21
|
+
require 'pp'
|
22
|
+
pp({
|
23
|
+
:unicorn_options => options,
|
24
|
+
:app => app,
|
25
|
+
:daemonize => options[:daemonize],
|
26
|
+
})
|
10
27
|
end
|
11
28
|
|
12
|
-
|
29
|
+
Unicorn::Launcher.daemonize!(options) if options[:daemonize]
|
30
|
+
Unicorn::HttpServer.new(app, options).start.join
|
data/config.ru
ADDED
data/hell.gemspec
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "hell"
|
8
|
+
s.version = "0.1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Jose Diaz-Gonzalez"]
|
12
|
+
s.date = "2013-02-21"
|
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
|
+
s.email = "jose@seatgeek.com"
|
15
|
+
s.executables = ["hell"]
|
16
|
+
s.extra_rdoc_files = [
|
17
|
+
"LICENSE.txt",
|
18
|
+
"README.markdown"
|
19
|
+
]
|
20
|
+
s.files = [
|
21
|
+
".document",
|
22
|
+
"Gemfile",
|
23
|
+
"LICENSE.txt",
|
24
|
+
"README.markdown",
|
25
|
+
"Rakefile",
|
26
|
+
"VERSION",
|
27
|
+
"bin/hell",
|
28
|
+
"config.ru",
|
29
|
+
"hell.gemspec",
|
30
|
+
"lib/hell.rb",
|
31
|
+
"lib/hell/app.rb",
|
32
|
+
"lib/hell/lib/capistrano/cli.rb",
|
33
|
+
"lib/hell/lib/capistrano/configuration.rb",
|
34
|
+
"lib/hell/lib/capistrano/task_definition.rb",
|
35
|
+
"lib/hell/lib/cli.rb",
|
36
|
+
"lib/hell/lib/helpers.rb",
|
37
|
+
"lib/hell/lib/monkey_patch.rb",
|
38
|
+
"lib/hell/public/assets/css/bootstrap-responsive.css",
|
39
|
+
"lib/hell/public/assets/css/bootstrap-responsive.min.css",
|
40
|
+
"lib/hell/public/assets/css/bootstrap.css",
|
41
|
+
"lib/hell/public/assets/css/bootstrap.min.css",
|
42
|
+
"lib/hell/public/assets/css/hell.css",
|
43
|
+
"lib/hell/public/assets/ico/favicon.ico",
|
44
|
+
"lib/hell/public/assets/ico/favicon.png",
|
45
|
+
"lib/hell/public/assets/img/glyphicons-halflings-white.png",
|
46
|
+
"lib/hell/public/assets/img/glyphicons-halflings.png",
|
47
|
+
"lib/hell/public/assets/js/backbone-localstorage.js",
|
48
|
+
"lib/hell/public/assets/js/backbone.js",
|
49
|
+
"lib/hell/public/assets/js/bankersbox.js",
|
50
|
+
"lib/hell/public/assets/js/bootstrap.growl.js",
|
51
|
+
"lib/hell/public/assets/js/bootstrap.js",
|
52
|
+
"lib/hell/public/assets/js/hashchange.js",
|
53
|
+
"lib/hell/public/assets/js/hell.js",
|
54
|
+
"lib/hell/public/assets/js/jquery.js",
|
55
|
+
"lib/hell/public/assets/js/timeago.js",
|
56
|
+
"lib/hell/public/assets/js/underscore.js",
|
57
|
+
"lib/hell/views/index.erb",
|
58
|
+
"test/helper.rb",
|
59
|
+
"test/test_hell.rb",
|
60
|
+
"unicorn"
|
61
|
+
]
|
62
|
+
s.homepage = "http://github.com/seatgeek/hell"
|
63
|
+
s.licenses = ["MIT"]
|
64
|
+
s.require_paths = ["lib"]
|
65
|
+
s.rubygems_version = "1.8.23"
|
66
|
+
s.summary = "A web interface and api wrapper around Capistrano"
|
67
|
+
|
68
|
+
if s.respond_to? :specification_version then
|
69
|
+
s.specification_version = 3
|
70
|
+
|
71
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
72
|
+
s.add_runtime_dependency(%q<capistrano>, [">= 0"])
|
73
|
+
s.add_runtime_dependency(%q<multi_json>, [">= 0"])
|
74
|
+
s.add_runtime_dependency(%q<pusher>, [">= 0"])
|
75
|
+
s.add_runtime_dependency(%q<sinatra>, [">= 0"])
|
76
|
+
s.add_runtime_dependency(%q<sinatra-contrib>, [">= 0"])
|
77
|
+
s.add_runtime_dependency(%q<sinatra-assetpack>, [">= 0"])
|
78
|
+
s.add_runtime_dependency(%q<sass>, [">= 0"])
|
79
|
+
s.add_runtime_dependency(%q<thin>, [">= 0"])
|
80
|
+
s.add_runtime_dependency(%q<unicorn>, [">= 0"])
|
81
|
+
s.add_development_dependency(%q<jeweler>, [">= 0"])
|
82
|
+
else
|
83
|
+
s.add_dependency(%q<capistrano>, [">= 0"])
|
84
|
+
s.add_dependency(%q<multi_json>, [">= 0"])
|
85
|
+
s.add_dependency(%q<pusher>, [">= 0"])
|
86
|
+
s.add_dependency(%q<sinatra>, [">= 0"])
|
87
|
+
s.add_dependency(%q<sinatra-contrib>, [">= 0"])
|
88
|
+
s.add_dependency(%q<sinatra-assetpack>, [">= 0"])
|
89
|
+
s.add_dependency(%q<sass>, [">= 0"])
|
90
|
+
s.add_dependency(%q<thin>, [">= 0"])
|
91
|
+
s.add_dependency(%q<unicorn>, [">= 0"])
|
92
|
+
s.add_dependency(%q<jeweler>, [">= 0"])
|
93
|
+
end
|
94
|
+
else
|
95
|
+
s.add_dependency(%q<capistrano>, [">= 0"])
|
96
|
+
s.add_dependency(%q<multi_json>, [">= 0"])
|
97
|
+
s.add_dependency(%q<pusher>, [">= 0"])
|
98
|
+
s.add_dependency(%q<sinatra>, [">= 0"])
|
99
|
+
s.add_dependency(%q<sinatra-contrib>, [">= 0"])
|
100
|
+
s.add_dependency(%q<sinatra-assetpack>, [">= 0"])
|
101
|
+
s.add_dependency(%q<sass>, [">= 0"])
|
102
|
+
s.add_dependency(%q<thin>, [">= 0"])
|
103
|
+
s.add_dependency(%q<unicorn>, [">= 0"])
|
104
|
+
s.add_dependency(%q<jeweler>, [">= 0"])
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
data/lib/hell.rb
CHANGED
data/lib/hell/app.rb
CHANGED
@@ -4,23 +4,36 @@ require 'sinatra'
|
|
4
4
|
require 'sinatra/base'
|
5
5
|
require 'sinatra/json'
|
6
6
|
require 'sinatra/streaming'
|
7
|
+
require 'sinatra/assetpack'
|
7
8
|
|
8
|
-
require '
|
9
|
+
require 'pusher'
|
10
|
+
require 'multi_json'
|
9
11
|
require 'securerandom'
|
10
|
-
require 'websocket'
|
11
|
-
|
12
|
-
HELL_DIR = Dir.pwd
|
13
|
-
APP_ROOT = ENV.fetch('HELL_APP_ROOT', HELL_DIR)
|
14
|
-
ENVIRONMENTS = ENV.fetch('HELL_ENVIRONMENTS', 'production,staging').split(',')
|
15
|
-
BLACKLIST = ['invoke', 'shell', 'internal:ensure_env', 'internal:setup_env']
|
16
|
-
REQUIRE_ENV = ENV.fetch('HELL_REQUIRE_ENV', '1') == '1'
|
17
|
-
HELL_LOG_PATH = ENV.fetch('HELL_LOG_PATH', File.join(HELL_DIR, 'log'))
|
18
|
-
HELL_BASE_DIR = ENV.fetch('HELL_BASE_DIR', '/')
|
19
|
-
SENTINEL_STRINGS = ENV.fetch('HELL_SENTINEL_STRINGS', 'Hellish Task Completed').split(',')
|
20
12
|
|
13
|
+
require 'hell/lib/cli'
|
21
14
|
require 'hell/lib/monkey_patch'
|
22
15
|
require 'hell/lib/helpers'
|
23
16
|
|
17
|
+
options, op = Hell::CLI.option_parser(ARGV, nil, true)
|
18
|
+
op = nil
|
19
|
+
|
20
|
+
HELL_DIR = Dir.pwd
|
21
|
+
HELL_APP_ROOT = options[:app_root]
|
22
|
+
HELL_BLACKLIST = ['invoke', 'shell', 'internal:ensure_env', 'internal:setup_env']
|
23
|
+
HELL_REQUIRE_ENV = !!options[:require_env]
|
24
|
+
HELL_LOG_PATH = options[:log_path]
|
25
|
+
HELL_BASE_PATH = options[:base_path]
|
26
|
+
HELL_SENTINEL_STRINGS = options[:sentinel]
|
27
|
+
|
28
|
+
USE_PUSHER = !!(options[:pusher_app_id] && options[:pusher_key] && options[:pusher_secret])
|
29
|
+
|
30
|
+
PUSHER_APP_ID = options[:pusher_app_id]
|
31
|
+
PUSHER_KEY = options[:pusher_key]
|
32
|
+
PUSHER_SECRET = options[:pusher_secret]
|
33
|
+
|
34
|
+
Pusher.app_id = PUSHER_APP_ID
|
35
|
+
Pusher.key = PUSHER_KEY
|
36
|
+
Pusher.secret = PUSHER_SECRET
|
24
37
|
|
25
38
|
module Hell
|
26
39
|
class App < Sinatra::Base
|
@@ -28,7 +41,7 @@ module Hell
|
|
28
41
|
helpers Sinatra::Streaming
|
29
42
|
helpers Hell::Helpers
|
30
43
|
|
31
|
-
|
44
|
+
register Sinatra::AssetPack
|
32
45
|
|
33
46
|
set :public_folder, File.join(File.expand_path('..', __FILE__), 'public')
|
34
47
|
set :root, HELL_DIR
|
@@ -36,38 +49,67 @@ module Hell
|
|
36
49
|
set :static, true
|
37
50
|
set :views, File.join(File.expand_path('..', __FILE__), 'views')
|
38
51
|
|
52
|
+
assets do
|
53
|
+
css_compression :sass
|
54
|
+
|
55
|
+
js :jquery, [
|
56
|
+
'/assets/js/jquery.js',
|
57
|
+
]
|
58
|
+
|
59
|
+
js :main, [
|
60
|
+
'/assets/js/backbone-localstorage.js',
|
61
|
+
'/assets/js/bootstrap.growl.js',
|
62
|
+
'/assets/js/timeago.js',
|
63
|
+
'/assets/js/hashchange.js',
|
64
|
+
'/assets/js/hell.js',
|
65
|
+
]
|
66
|
+
|
67
|
+
css :bootstrap, [
|
68
|
+
'/assets/css/bootstrap.min.css',
|
69
|
+
'/assets/css/bootstrap-responsive.css',
|
70
|
+
'/assets/css/hell.css',
|
71
|
+
]
|
72
|
+
|
73
|
+
prebuild true
|
74
|
+
end
|
75
|
+
|
39
76
|
configure :production, :development do
|
40
77
|
enable :logging
|
41
78
|
end
|
42
79
|
|
43
80
|
get '/' do
|
44
|
-
@tasks = cap.
|
45
|
-
@require_env =
|
46
|
-
@www_base_dir =
|
47
|
-
@environments =
|
81
|
+
@tasks = cap.tasks.keys
|
82
|
+
@require_env = HELL_REQUIRE_ENV
|
83
|
+
@www_base_dir = HELL_BASE_PATH
|
84
|
+
@environments = cap.environments
|
85
|
+
|
86
|
+
@use_pusher = USE_PUSHER
|
87
|
+
@pusher_app_id = PUSHER_APP_ID
|
88
|
+
@pusher_key = PUSHER_KEY
|
89
|
+
@pusher_secret = PUSHER_SECRET
|
48
90
|
erb :index
|
49
91
|
end
|
50
92
|
|
51
93
|
get '/tasks' do
|
52
|
-
tasks = cap.
|
94
|
+
tasks = cap.tasks
|
53
95
|
json tasks
|
54
96
|
end
|
55
97
|
|
56
98
|
get '/tasks/search/:pattern' do
|
57
|
-
tasks = cap.
|
99
|
+
tasks = cap.tasks(params[:pattern])
|
58
100
|
json tasks
|
59
101
|
end
|
60
102
|
|
61
103
|
get '/tasks/:name/exists' do
|
62
|
-
tasks = cap.
|
104
|
+
tasks = cap.tasks(params[:name], {:exact => true})
|
63
105
|
response = { :exists => !tasks.empty?, :task => params[:name]}
|
64
106
|
json response
|
65
107
|
end
|
66
108
|
|
67
|
-
|
109
|
+
put '/tasks/:name/background' do
|
68
110
|
tasks, original_cmd = verify_task(cap, params[:name])
|
69
111
|
verbose = ""
|
70
|
-
verbose = "LOGGING=debug" if params[:verbose] ==
|
112
|
+
verbose = "LOGGING=debug" if params[:verbose] == "on"
|
71
113
|
|
72
114
|
task_id = run_in_background!("bundle exec cap -l STDOUT %s %s" % [original_cmd, verbose]) unless tasks.empty?
|
73
115
|
response = {}
|
@@ -80,9 +122,9 @@ module Hell
|
|
80
122
|
get '/logs/:id/tail' do
|
81
123
|
content_type "text/event-stream"
|
82
124
|
if valid_log params[:id]
|
83
|
-
|
125
|
+
send_success(params[:id], "tail -f %s" % File.join(HELL_LOG_PATH, params[:id] + ".log"))
|
84
126
|
else
|
85
|
-
|
127
|
+
send_error(params[:id], "log file '#{params[:id]}' not found")
|
86
128
|
end
|
87
129
|
end
|
88
130
|
|
@@ -94,35 +136,69 @@ module Hell
|
|
94
136
|
|
95
137
|
get '/tasks/:name/execute' do
|
96
138
|
tasks, original_cmd = verify_task(cap, params[:name])
|
139
|
+
task_id = random_id
|
97
140
|
content_type "text/event-stream"
|
98
141
|
if tasks.empty?
|
99
|
-
|
142
|
+
send_error(task_id, "cap task '#{original_cmd}' not found")
|
100
143
|
else
|
101
|
-
|
144
|
+
send_success(task_id, "bundle exec cap -l STDOUT #{original_cmd} LOGGING=debug 2>&1", {:prepend => true})
|
102
145
|
end
|
103
146
|
end
|
104
147
|
|
105
|
-
def
|
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
|
+
def cap
|
159
|
+
FileUtils.chdir HELL_APP_ROOT do
|
160
|
+
@cap ||= Capistrano::CLI.parse(["-T"])
|
161
|
+
end
|
162
|
+
return @cap
|
163
|
+
end
|
164
|
+
|
165
|
+
def _pusher_error(task_id, message)
|
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)
|
106
182
|
stream do |out|
|
107
183
|
out << "event: start\ndata:\n\n" unless out.closed?
|
108
|
-
out << "data: " + ws_message("<p>#{message}</p>") unless out.closed?
|
184
|
+
out << "data: " + ws_message("<p>#{message}</p>") + "\n\n" unless out.closed?
|
109
185
|
out << "event: end\ndata:\n\n" unless out.closed?
|
110
186
|
out.close
|
111
187
|
end
|
112
188
|
end
|
113
189
|
|
114
|
-
def _stream_success(command, opts = {})
|
190
|
+
def _stream_success(task_id, command, opts = {})
|
115
191
|
opts = {:prepend => false}.merge(opts)
|
116
192
|
stream do |out|
|
117
193
|
out << "event: start\ndata:\n\n" unless out.closed?
|
118
|
-
out << "data: " + ws_message("<p>#{command}</p>") unless out.closed? or opts[:prepend] == false
|
194
|
+
out << "data: " + ws_message("<p>#{command}</p>") + "\n\n" unless out.closed? or opts[:prepend] == false
|
119
195
|
IO.popen(command, 'rb') do |io|
|
120
|
-
io.each
|
121
|
-
process_line(line, out, io)
|
122
|
-
end
|
196
|
+
io.each {|line| stream_line(task_id, line, out, io)}
|
123
197
|
end
|
124
198
|
close_stream(out)
|
125
199
|
end
|
126
200
|
end
|
201
|
+
|
202
|
+
init_send
|
127
203
|
end
|
128
204
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'capistrano/cli'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Capistrano
|
5
|
+
class CLI
|
6
|
+
module JsonTaskList
|
7
|
+
|
8
|
+
def task_list
|
9
|
+
config = instantiate_configuration(options)
|
10
|
+
config.debug = options[:debug]
|
11
|
+
config.dry_run = options[:dry_run]
|
12
|
+
config.preserve_roles = options[:preserve_roles]
|
13
|
+
config.logger.level = options[:verbose]
|
14
|
+
|
15
|
+
set_pre_vars(config)
|
16
|
+
load_recipes(config)
|
17
|
+
|
18
|
+
config.trigger(:load)
|
19
|
+
[config, options]
|
20
|
+
|
21
|
+
tasks = config.task_list(:all)
|
22
|
+
tasks.reject! {|t| HELL_BLACKLIST.include?(t.fully_qualified_name)}
|
23
|
+
tasks = Hash[tasks.map {|task| [task.fully_qualified_name, task.to_hash]}]
|
24
|
+
|
25
|
+
config.trigger(:exit)
|
26
|
+
|
27
|
+
tasks
|
28
|
+
end
|
29
|
+
|
30
|
+
def tasks(pattern = nil, opts = {})
|
31
|
+
@available_tasks ||= task_list
|
32
|
+
tasks = @available_tasks.reject {|name, task| is_environment?(task)}
|
33
|
+
|
34
|
+
if pattern.is_a?(String)
|
35
|
+
if opts.fetch(:exact, false)
|
36
|
+
tasks.select! {|name| name == pattern}
|
37
|
+
else
|
38
|
+
tasks.select! {|name| name =~ /#{pattern}/}
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
tasks
|
43
|
+
end
|
44
|
+
|
45
|
+
def environments
|
46
|
+
@available_tasks ||= task_list
|
47
|
+
environments = @available_tasks.select {|name, task| is_environment?(task)}
|
48
|
+
environments.keys
|
49
|
+
end
|
50
|
+
|
51
|
+
def is_environment?(task)
|
52
|
+
return false unless task[:options].include?(:hell_tags)
|
53
|
+
Array(task[:options][:hell_tags]).include?(:environment)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|