pogo 2.31.2 → 2.32.14
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/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
|
|