pogo 2.31.2 → 2.32.14
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/pogo +5 -4
- data/lib/heroku.rb +6 -11
- data/lib/heroku/auth.rb +22 -5
- data/lib/heroku/client/heroku_postgresql.rb +52 -22
- data/lib/heroku/client/rendezvous.rb +5 -2
- data/lib/heroku/command.rb +42 -21
- data/lib/heroku/command/apps.rb +2 -2
- data/lib/heroku/command/labs.rb +65 -79
- data/lib/heroku/command/pg.rb +55 -66
- data/lib/heroku/command/pgbackups.rb +29 -9
- data/lib/heroku/command/plugins.rb +1 -1
- data/lib/heroku/command/run.rb +3 -1
- data/lib/heroku/command/status.rb +1 -1
- data/lib/heroku/excon.rb +9 -0
- data/lib/heroku/helpers.rb +4 -0
- data/lib/heroku/helpers/heroku_postgresql.rb +110 -56
- data/lib/heroku/updater.rb +70 -66
- data/lib/heroku/version.rb +1 -1
- data/spec/heroku/client/heroku_postgresql_spec.rb +55 -18
- data/spec/heroku/command/addons_spec.rb +18 -2
- data/spec/heroku/command/labs_spec.rb +10 -9
- data/spec/heroku/command/pg_spec.rb +56 -43
- data/spec/heroku/command/pgbackups_spec.rb +28 -6
- data/spec/heroku/command_spec.rb +2 -2
- data/spec/heroku/helpers/heroku_postgresql_spec.rb +65 -47
- metadata +6 -5
data/bin/pogo
CHANGED
@@ -2,10 +2,10 @@
|
|
2
2
|
# encoding: UTF-8
|
3
3
|
|
4
4
|
# pogo defaults
|
5
|
-
ENV['HEROKU_HOST'] ||= '
|
6
|
-
ENV['HEROKU_STATUS_HOST'] ||= '
|
7
|
-
ENV['
|
8
|
-
ENV['
|
5
|
+
ENV['HEROKU_HOST'] ||= 'pogoapp.com'
|
6
|
+
ENV['HEROKU_STATUS_HOST'] ||= 'status.pogoapp.com'
|
7
|
+
ENV['HEROKU_GIT_HOST'] ||= 'git.pogoapp.com'
|
8
|
+
ENV['HEROKU_SSL_VERIFY'] ||= 'disable' # StartCom cert + RestClient = :(
|
9
9
|
|
10
10
|
# resolve bin path, ignoring symlinks
|
11
11
|
require "pathname"
|
@@ -19,4 +19,5 @@ Heroku::Updater.disable("`heroku update` is only available from Heroku Toolbelt.
|
|
19
19
|
|
20
20
|
# start up the CLI
|
21
21
|
require "heroku/cli"
|
22
|
+
Heroku.user_agent = "heroku-gem/#{Heroku::VERSION} (#{RUBY_PLATFORM}) ruby/#{RUBY_VERSION}"
|
22
23
|
Heroku::CLI.start(*ARGV)
|
data/lib/heroku.rb
CHANGED
@@ -4,19 +4,14 @@ require "heroku/version"
|
|
4
4
|
|
5
5
|
module Heroku
|
6
6
|
|
7
|
-
USER_AGENT =
|
7
|
+
USER_AGENT = "heroku-gem/#{Heroku::VERSION} (#{RUBY_PLATFORM}) ruby/#{RUBY_VERSION}"
|
8
8
|
|
9
9
|
def self.user_agent
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
user_agent = "heroku-#{type}/#{Heroku::Updater.latest_local_version} (#{RUBY_PLATFORM}) ruby/#{RUBY_VERSION}"
|
16
|
-
if Heroku::Updater.autoupdate?
|
17
|
-
user_agent << ' autoupdate'
|
18
|
-
end
|
19
|
-
user_agent
|
10
|
+
@@user_agent ||= USER_AGENT
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.user_agent=(agent)
|
14
|
+
@@user_agent = agent
|
20
15
|
end
|
21
16
|
|
22
17
|
end
|
data/lib/heroku/auth.rb
CHANGED
@@ -178,7 +178,7 @@ class Heroku::Auth
|
|
178
178
|
end
|
179
179
|
|
180
180
|
def ask_for_credentials
|
181
|
-
puts "Enter your
|
181
|
+
puts "Enter your #{host_name} credentials."
|
182
182
|
|
183
183
|
print "Email: "
|
184
184
|
user = ask
|
@@ -300,12 +300,29 @@ class Heroku::Auth
|
|
300
300
|
@login_attempts < 3
|
301
301
|
end
|
302
302
|
|
303
|
+
def verified_hosts
|
304
|
+
%w( heroku.com heroku-shadow.com )
|
305
|
+
end
|
306
|
+
|
307
|
+
def base_host(host)
|
308
|
+
URI.parse(full_host(host)).host.split(".")[-2..-1].join(".")
|
309
|
+
end
|
310
|
+
|
311
|
+
def full_host(host)
|
312
|
+
(host =~ /^http/) ? host : "https://api.#{host}"
|
313
|
+
end
|
314
|
+
|
315
|
+
def verify_host?(host)
|
316
|
+
hostname = base_host(host)
|
317
|
+
verified = verified_hosts.include?(hostname)
|
318
|
+
verified = false if ENV["HEROKU_SSL_VERIFY"] == "disable"
|
319
|
+
verified
|
320
|
+
end
|
321
|
+
|
303
322
|
protected
|
304
323
|
|
305
324
|
def default_params
|
306
|
-
|
307
|
-
verify_ssl = ENV['HEROKU_SSL_VERIFY'] != 'disable' && full_host =~ %r|^https://api.heroku.com|
|
308
|
-
uri = URI(full_host)
|
325
|
+
uri = URI.parse(full_host(host))
|
309
326
|
{
|
310
327
|
:headers => {
|
311
328
|
'User-Agent' => Heroku.user_agent
|
@@ -313,7 +330,7 @@ class Heroku::Auth
|
|
313
330
|
:host => uri.host,
|
314
331
|
:port => uri.port.to_s,
|
315
332
|
:scheme => uri.scheme,
|
316
|
-
:ssl_verify_peer =>
|
333
|
+
:ssl_verify_peer => verify_host?(host)
|
317
334
|
}
|
318
335
|
end
|
319
336
|
end
|
@@ -1,51 +1,81 @@
|
|
1
1
|
require "heroku/client"
|
2
|
-
require "digest/sha2"
|
3
2
|
|
4
3
|
class Heroku::Client::HerokuPostgresql
|
5
|
-
Version =
|
4
|
+
Version = 11
|
6
5
|
|
7
6
|
include Heroku::Helpers
|
8
7
|
|
9
|
-
|
8
|
+
@headers = { :x_heroku_gem_version => Heroku::Client.version }
|
9
|
+
|
10
|
+
def self.add_headers(headers)
|
11
|
+
@headers.merge! headers
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.headers
|
15
|
+
@headers
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :attachment
|
19
|
+
def initialize(attachment)
|
20
|
+
@attachment = attachment
|
21
|
+
if attachment.resource_name == 'SHARED_DATABASE'
|
22
|
+
error('This command is not available for shared database')
|
23
|
+
end
|
10
24
|
require 'rest_client'
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
25
|
+
end
|
26
|
+
|
27
|
+
def heroku_postgresql_host
|
28
|
+
if attachment.starter_plan?
|
29
|
+
ENV["HEROKU_POSTGRESQL_HOST"] || "postgres-starter-api"
|
30
|
+
else
|
31
|
+
if ENV['SHOGUN']
|
32
|
+
"shogun-#{ENV['SHOGUN']}"
|
33
|
+
else
|
34
|
+
ENV["HEROKU_POSTGRESQL_HOST"] || "postgres-api"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def resource_name
|
40
|
+
attachment.resource_name
|
41
|
+
end
|
42
|
+
|
43
|
+
def heroku_postgresql_resource
|
44
|
+
RestClient::Resource.new(
|
45
|
+
"https://#{heroku_postgresql_host}.heroku.com/client/v11/databases",
|
46
|
+
:user => Heroku::Auth.user,
|
47
|
+
:password => Heroku::Auth.password,
|
48
|
+
:headers => self.class.headers
|
16
49
|
)
|
17
50
|
end
|
18
51
|
|
19
52
|
def ingress
|
20
|
-
http_put "#{
|
53
|
+
http_put "#{resource_name}/ingress"
|
21
54
|
end
|
22
55
|
|
23
56
|
def reset
|
24
|
-
http_put "#{
|
57
|
+
http_put "#{resource_name}/reset"
|
25
58
|
end
|
26
59
|
|
27
60
|
def rotate_credentials
|
28
|
-
http_post "#{
|
61
|
+
http_post "#{resource_name}/credentials_rotation"
|
29
62
|
end
|
30
63
|
|
31
|
-
def get_database
|
32
|
-
|
64
|
+
def get_database(extended=false)
|
65
|
+
query = extended ? '?extended=true' : ''
|
66
|
+
http_get resource_name + query
|
33
67
|
end
|
34
68
|
|
35
69
|
def get_wait_status
|
36
|
-
http_get "#{
|
70
|
+
http_get "#{resource_name}/wait_status"
|
37
71
|
end
|
38
72
|
|
39
73
|
def unfollow
|
40
|
-
http_put "#{
|
74
|
+
http_put "#{resource_name}/unfollow"
|
41
75
|
end
|
42
76
|
|
43
77
|
protected
|
44
78
|
|
45
|
-
def sha(url)
|
46
|
-
Digest::SHA2.hexdigest url
|
47
|
-
end
|
48
|
-
|
49
79
|
def sym_keys(c)
|
50
80
|
if c.is_a?(Array)
|
51
81
|
c.map { |e| sym_keys(e) }
|
@@ -77,7 +107,7 @@ class Heroku::Client::HerokuPostgresql
|
|
77
107
|
def http_get(path)
|
78
108
|
checking_client_version do
|
79
109
|
retry_on_exception(RestClient::Exception) do
|
80
|
-
response =
|
110
|
+
response = heroku_postgresql_resource[path].get
|
81
111
|
display_heroku_warning response
|
82
112
|
sym_keys(json_decode(response.to_s))
|
83
113
|
end
|
@@ -86,7 +116,7 @@ class Heroku::Client::HerokuPostgresql
|
|
86
116
|
|
87
117
|
def http_post(path, payload = {})
|
88
118
|
checking_client_version do
|
89
|
-
response =
|
119
|
+
response = heroku_postgresql_resource[path].post(json_encode(payload))
|
90
120
|
display_heroku_warning response
|
91
121
|
sym_keys(json_decode(response.to_s))
|
92
122
|
end
|
@@ -94,7 +124,7 @@ class Heroku::Client::HerokuPostgresql
|
|
94
124
|
|
95
125
|
def http_put(path, payload = {})
|
96
126
|
checking_client_version do
|
97
|
-
response =
|
127
|
+
response = heroku_postgresql_resource[path].put(json_encode(payload))
|
98
128
|
display_heroku_warning response
|
99
129
|
sym_keys(json_decode(response.to_s))
|
100
130
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require "timeout"
|
2
2
|
require "socket"
|
3
3
|
require "uri"
|
4
|
+
require "heroku/auth"
|
4
5
|
require "heroku/client"
|
5
6
|
require "heroku/helpers"
|
6
7
|
|
@@ -29,10 +30,12 @@ class Heroku::Client::Rendezvous
|
|
29
30
|
|
30
31
|
ssl_socket = Timeout.timeout(connect_timeout) do
|
31
32
|
ssl_context = OpenSSL::SSL::SSLContext.new
|
32
|
-
|
33
|
-
|
33
|
+
|
34
|
+
if Heroku::Auth.verify_host?(host)
|
35
|
+
ssl_context.ca_file = File.expand_path("../../../../data/cacert.pem", __FILE__)
|
34
36
|
ssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
35
37
|
end
|
38
|
+
|
36
39
|
tcp_socket = TCPSocket.open(host, port)
|
37
40
|
ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, ssl_context)
|
38
41
|
ssl_socket.connect
|
data/lib/heroku/command.rb
CHANGED
@@ -115,20 +115,12 @@ module Heroku
|
|
115
115
|
if args.include?('-h') || args.include?('--help')
|
116
116
|
args.unshift(cmd) unless cmd =~ /^-.*/
|
117
117
|
cmd = 'help'
|
118
|
-
command = parse(
|
118
|
+
command = parse(cmd)
|
119
119
|
end
|
120
120
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
command = parse(cmd)
|
125
|
-
else
|
126
|
-
error([
|
127
|
-
"`#{cmd}` is not a heroku command.",
|
128
|
-
suggestion(cmd, commands.keys + command_aliases.keys),
|
129
|
-
"See `heroku help` for a list of available commands."
|
130
|
-
].compact.join("\n"))
|
131
|
-
end
|
121
|
+
if cmd == '--version'
|
122
|
+
cmd = 'version'
|
123
|
+
command = parse(cmd)
|
132
124
|
end
|
133
125
|
|
134
126
|
@current_command = cmd
|
@@ -141,7 +133,7 @@ module Heroku
|
|
141
133
|
# remove OptionParsers Officious['version'] to avoid conflicts
|
142
134
|
# see: https://github.com/ruby/ruby/blob/trunk/lib/optparse.rb#L814
|
143
135
|
parser.base.long.delete('version')
|
144
|
-
(global_options + command[:options]).each do |option|
|
136
|
+
(global_options + (command && command[:options] || [])).each do |option|
|
145
137
|
parser.on(*option[:args]) do |value|
|
146
138
|
if option[:proc]
|
147
139
|
option[:proc].call(value)
|
@@ -173,16 +165,39 @@ module Heroku
|
|
173
165
|
@current_options = opts
|
174
166
|
@invalid_arguments = invalid_options
|
175
167
|
|
176
|
-
command_instance = command[:klass].new(args.dup, opts.dup)
|
177
|
-
|
178
168
|
@anonymous_command = [ARGV.first, *@anonymized_args].join(' ')
|
179
|
-
|
180
|
-
|
181
|
-
|
169
|
+
begin
|
170
|
+
usage_directory = "#{home_directory}/.heroku/usage"
|
171
|
+
FileUtils.mkdir_p(usage_directory)
|
172
|
+
usage_file = usage_directory << "/#{Heroku::VERSION}"
|
173
|
+
usage = if File.exists?(usage_file)
|
174
|
+
json_decode(File.read(usage_file))
|
175
|
+
else
|
176
|
+
{}
|
177
|
+
end
|
178
|
+
usage[@anonymous_command] ||= 0
|
179
|
+
usage[@anonymous_command] += 1
|
180
|
+
File.write(usage_file, json_encode(usage) + "\n")
|
181
|
+
rescue
|
182
|
+
# usage writing is not important, allow failures
|
182
183
|
end
|
183
|
-
@normalized_command = [ARGV.first, @normalized_args.sort_by {|arg| arg.gsub('-', '')}].join(' ')
|
184
184
|
|
185
|
-
|
185
|
+
if command
|
186
|
+
command_instance = command[:klass].new(args.dup, opts.dup)
|
187
|
+
|
188
|
+
if !@normalized_args.include?('--app _') && (implied_app = command_instance.app rescue nil)
|
189
|
+
@normalized_args << '--app _'
|
190
|
+
end
|
191
|
+
@normalized_command = [ARGV.first, @normalized_args.sort_by {|arg| arg.gsub('-', '')}].join(' ')
|
192
|
+
|
193
|
+
[ command_instance, command[:method] ]
|
194
|
+
else
|
195
|
+
error([
|
196
|
+
"`#{cmd}` is not a heroku command.",
|
197
|
+
suggestion(cmd, commands.keys + command_aliases.keys),
|
198
|
+
"See `heroku help` for a list of available commands."
|
199
|
+
].compact.join("\n"))
|
200
|
+
end
|
186
201
|
end
|
187
202
|
|
188
203
|
def self.run(cmd, arguments=[])
|
@@ -233,6 +248,12 @@ module Heroku
|
|
233
248
|
error e.message
|
234
249
|
rescue OptionParser::ParseError
|
235
250
|
commands[cmd] ? run("help", [cmd]) : run("help")
|
251
|
+
rescue Excon::Errors::SocketError => e
|
252
|
+
if e.message == 'getaddrinfo: nodename nor servname provided, or not known (SocketError)'
|
253
|
+
error("Unable to connect to Heroku API, please check internet connectivity and try again.")
|
254
|
+
else
|
255
|
+
raise(e)
|
256
|
+
end
|
236
257
|
ensure
|
237
258
|
display_warnings
|
238
259
|
end
|
@@ -242,7 +263,7 @@ module Heroku
|
|
242
263
|
end
|
243
264
|
|
244
265
|
def self.extract_error(body, options={})
|
245
|
-
default_error = block_given? ? yield : "Internal server error.\nRun
|
266
|
+
default_error = block_given? ? yield : "Internal server error.\nRun `heroku status` to check for known platform issues."
|
246
267
|
parse_error_xml(body) || parse_error_json(body) || parse_error_plain(body) || default_error
|
247
268
|
end
|
248
269
|
|
data/lib/heroku/command/apps.rb
CHANGED
@@ -175,7 +175,7 @@ class Heroku::Command::Apps < Heroku::Command::Base
|
|
175
175
|
# $ heroku apps:create myapp-staging --remote staging
|
176
176
|
#
|
177
177
|
def create
|
178
|
-
name = shift_argument || options[:app]
|
178
|
+
name = shift_argument || options[:app] || ENV['HEROKU_APP']
|
179
179
|
validate_arguments!
|
180
180
|
|
181
181
|
info = api.post_app({ "name" => name, "stack" => options[:stack] }).body
|
@@ -206,7 +206,7 @@ class Heroku::Command::Apps < Heroku::Command::Base
|
|
206
206
|
|
207
207
|
hputs([ info["web_url"], info["git_url"] ].join(" | "))
|
208
208
|
rescue Timeout::Error
|
209
|
-
hputs("Timed Out!
|
209
|
+
hputs("Timed Out! Run `heroku status` to check for known platform issues.")
|
210
210
|
end
|
211
211
|
|
212
212
|
unless options[:no_remote].is_a? FalseClass
|
data/lib/heroku/command/labs.rb
CHANGED
@@ -4,29 +4,39 @@ require "heroku/command/base"
|
|
4
4
|
#
|
5
5
|
class Heroku::Command::Labs < Heroku::Command::Base
|
6
6
|
|
7
|
-
# labs
|
7
|
+
# labs
|
8
8
|
#
|
9
|
-
#
|
9
|
+
# list experimental features
|
10
10
|
#
|
11
11
|
#Example:
|
12
12
|
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
# sigterm-all: When stopping a dyno, send SIGTERM to all processes rather than only to the root process.
|
13
|
+
# === User Features (david@heroku.com)
|
14
|
+
# [+] dashboard Use Heroku Dashboard by default
|
16
15
|
#
|
17
|
-
# ===
|
18
|
-
#
|
16
|
+
# === App Features (glacial-retreat-5913)
|
17
|
+
# [ ] preboot Provide seamless web dyno deploys
|
18
|
+
# [ ] user-env-compile Add user config vars to the environment during slug compilation # $ heroku labs -a myapp
|
19
19
|
#
|
20
20
|
def index
|
21
21
|
validate_arguments!
|
22
22
|
|
23
|
-
|
24
|
-
|
23
|
+
user_features, app_features = api.get_features(app).body.sort_by do |feature|
|
24
|
+
feature["name"]
|
25
|
+
end.partition do |feature|
|
26
|
+
feature["kind"] == "user"
|
25
27
|
end
|
26
28
|
|
27
|
-
|
29
|
+
display_app = app || "no app specified"
|
30
|
+
|
31
|
+
styled_header "User Features (#{Heroku::Auth.user})"
|
32
|
+
display_features user_features
|
33
|
+
display
|
34
|
+
styled_header "App Features (#{display_app})"
|
35
|
+
display_features app_features
|
28
36
|
end
|
29
37
|
|
38
|
+
alias_command "labs:list", "labs"
|
39
|
+
|
30
40
|
# labs:info FEATURE
|
31
41
|
#
|
32
42
|
# displays additional information about FEATURE
|
@@ -54,107 +64,83 @@ class Heroku::Command::Labs < Heroku::Command::Base
|
|
54
64
|
|
55
65
|
# labs:disable FEATURE
|
56
66
|
#
|
57
|
-
# disables
|
67
|
+
# disables an experimental feature
|
58
68
|
#
|
59
69
|
#Example:
|
60
70
|
#
|
61
|
-
# $ heroku labs:disable
|
62
|
-
# Disabling
|
71
|
+
# $ heroku labs:disable ninja-power
|
72
|
+
# Disabling ninja-power feature for me@example.org... done
|
63
73
|
#
|
64
74
|
def disable
|
65
|
-
|
66
|
-
|
67
|
-
end
|
75
|
+
feature_name = shift_argument
|
76
|
+
error "Usage: heroku labs:disable FEATURE\nMust specify FEATURE to disable." unless feature_name
|
68
77
|
validate_arguments!
|
69
78
|
|
70
|
-
|
71
|
-
message
|
72
|
-
|
73
|
-
|
79
|
+
feature = api.get_features(app).body.detect { |f| f["name"] == feature_name }
|
80
|
+
message = "Disabling #{feature_name} "
|
81
|
+
|
82
|
+
error "No such feature: #{feature_name}" unless feature
|
83
|
+
|
84
|
+
if feature["kind"] == "user"
|
85
|
+
message += "for #{Heroku::Auth.user}"
|
86
|
+
else
|
87
|
+
error "Must specify an app" unless app
|
88
|
+
message += "for #{app}"
|
89
|
+
end
|
90
|
+
|
91
|
+
action message do
|
92
|
+
api.delete_feature feature_name, app
|
74
93
|
end
|
75
94
|
end
|
76
95
|
|
77
96
|
# labs:enable FEATURE
|
78
97
|
#
|
79
|
-
# enables
|
98
|
+
# enables an experimental feature
|
80
99
|
#
|
81
100
|
#Example:
|
82
101
|
#
|
83
|
-
# $ heroku labs:enable
|
84
|
-
# Enabling
|
85
|
-
# For more information see: http://devcenter.heroku.com/articles/labs-user-env-compile
|
102
|
+
# $ heroku labs:enable ninja-power
|
103
|
+
# Enabling ninja-power feature for me@example.org... done
|
86
104
|
#
|
87
105
|
def enable
|
88
|
-
|
89
|
-
|
90
|
-
end
|
106
|
+
feature_name = shift_argument
|
107
|
+
error "Usage: heroku labs:enable FEATURE\nMust specify FEATURE to enable." unless feature_name
|
91
108
|
validate_arguments!
|
92
109
|
|
93
|
-
|
94
|
-
message
|
95
|
-
|
96
|
-
|
97
|
-
|
110
|
+
feature = api.get_features.body.detect { |f| f["name"] == feature_name }
|
111
|
+
message = "Enabling #{feature_name} "
|
112
|
+
|
113
|
+
error "No such feature: #{feature_name}" unless feature
|
114
|
+
|
115
|
+
if feature["kind"] == "user"
|
116
|
+
message += "for #{Heroku::Auth.user}"
|
117
|
+
else
|
118
|
+
error "Must specify an app" unless app
|
119
|
+
message += "for #{app}"
|
98
120
|
end
|
99
|
-
display("WARNING: This feature is experimental and may change or be removed without notice.")
|
100
|
-
display("For more information see: #{feature_data['docs']}")
|
101
|
-
end
|
102
121
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
#
|
107
|
-
#Example:
|
108
|
-
#
|
109
|
-
# $ heroku labs:list
|
110
|
-
# === App Available Features
|
111
|
-
# dot-profile: Source .profile during dyno startup
|
112
|
-
# preboot: Provide seamless deploys by booting web dynos with new code before killing existing web dynos.
|
113
|
-
# user_env_compile: Add user config vars to the environment during slug compilation
|
114
|
-
#
|
115
|
-
# === User Available Features
|
116
|
-
# default-heroku-postgresql-dev: Use the new heroku-postgresql:dev add-on as the default database for Cedar apps.
|
117
|
-
#
|
118
|
-
def list
|
119
|
-
validate_arguments!
|
122
|
+
feature_data = action(message) do
|
123
|
+
api.post_feature(feature_name, app).body
|
124
|
+
end
|
120
125
|
|
121
|
-
|
122
|
-
|
126
|
+
display "WARNING: This feature is experimental and may change or be removed without notice."
|
127
|
+
display "For more information see: #{feature_data["docs"]}" if feature_data["docs"]
|
123
128
|
end
|
124
129
|
|
125
130
|
private
|
126
131
|
|
132
|
+
# app is not required for these commands, so rescue if there is none
|
127
133
|
def app
|
128
|
-
# app is not required for these commands, so rescue if there is none
|
129
134
|
super
|
130
135
|
rescue Heroku::Command::CommandFailed
|
131
136
|
nil
|
132
137
|
end
|
133
138
|
|
134
|
-
def display_features(
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
selected_features = selected_features.select {|feature| feature[key] == value}
|
140
|
-
end
|
141
|
-
|
142
|
-
feature_hash = {}
|
143
|
-
selected_features.each do |feature|
|
144
|
-
feature_hash[feature['name']] = feature['summary']
|
145
|
-
end
|
146
|
-
|
147
|
-
if feature_hash.empty?
|
148
|
-
case status
|
149
|
-
when 'available'
|
150
|
-
display("There are no #{type} features available.")
|
151
|
-
when 'enabled'
|
152
|
-
display("#{type} has no enabled features.")
|
153
|
-
end
|
154
|
-
else
|
155
|
-
styled_header("#{type} #{status.capitalize} Features")
|
156
|
-
styled_hash(feature_hash)
|
157
|
-
display
|
139
|
+
def display_features(features)
|
140
|
+
longest_name = features.map { |f| f["name"].to_s.length }.sort.last
|
141
|
+
features.each do |feature|
|
142
|
+
toggle = feature["enabled"] ? "[+]" : "[ ]"
|
143
|
+
display "%s %-#{longest_name}s %s" % [ toggle, feature["name"], feature["summary"] ]
|
158
144
|
end
|
159
145
|
end
|
160
146
|
|