pogo 2.31.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +73 -0
- data/bin/pogo +22 -0
- data/data/cacert.pem +3988 -0
- data/lib/heroku.rb +22 -0
- data/lib/heroku/auth.rb +320 -0
- data/lib/heroku/cli.rb +38 -0
- data/lib/heroku/client.rb +764 -0
- data/lib/heroku/client/heroku_postgresql.rb +111 -0
- data/lib/heroku/client/pgbackups.rb +113 -0
- data/lib/heroku/client/rendezvous.rb +105 -0
- data/lib/heroku/client/ssl_endpoint.rb +25 -0
- data/lib/heroku/command.rb +273 -0
- data/lib/heroku/command/account.rb +23 -0
- data/lib/heroku/command/accounts.rb +34 -0
- data/lib/heroku/command/addons.rb +305 -0
- data/lib/heroku/command/apps.rb +311 -0
- data/lib/heroku/command/auth.rb +86 -0
- data/lib/heroku/command/base.rb +230 -0
- data/lib/heroku/command/certs.rb +148 -0
- data/lib/heroku/command/config.rb +137 -0
- data/lib/heroku/command/db.rb +218 -0
- data/lib/heroku/command/domains.rb +85 -0
- data/lib/heroku/command/drains.rb +46 -0
- data/lib/heroku/command/git.rb +65 -0
- data/lib/heroku/command/help.rb +163 -0
- data/lib/heroku/command/keys.rb +115 -0
- data/lib/heroku/command/labs.rb +161 -0
- data/lib/heroku/command/logs.rb +98 -0
- data/lib/heroku/command/maintenance.rb +61 -0
- data/lib/heroku/command/pg.rb +277 -0
- data/lib/heroku/command/pgbackups.rb +289 -0
- data/lib/heroku/command/plugins.rb +110 -0
- data/lib/heroku/command/ps.rb +232 -0
- data/lib/heroku/command/releases.rb +124 -0
- data/lib/heroku/command/run.rb +179 -0
- data/lib/heroku/command/sharing.rb +89 -0
- data/lib/heroku/command/ssl.rb +61 -0
- data/lib/heroku/command/stack.rb +62 -0
- data/lib/heroku/command/status.rb +51 -0
- data/lib/heroku/command/update.rb +47 -0
- data/lib/heroku/command/version.rb +23 -0
- data/lib/heroku/deprecated.rb +5 -0
- data/lib/heroku/deprecated/help.rb +38 -0
- data/lib/heroku/distribution.rb +9 -0
- data/lib/heroku/helpers.rb +517 -0
- data/lib/heroku/helpers/heroku_postgresql.rb +104 -0
- data/lib/heroku/plugin.rb +161 -0
- data/lib/heroku/updater.rb +158 -0
- data/lib/heroku/version.rb +3 -0
- data/lib/vendor/heroku/okjson.rb +598 -0
- data/spec/helper/legacy_help.rb +16 -0
- data/spec/heroku/auth_spec.rb +246 -0
- data/spec/heroku/client/heroku_postgresql_spec.rb +34 -0
- data/spec/heroku/client/pgbackups_spec.rb +43 -0
- data/spec/heroku/client/rendezvous_spec.rb +62 -0
- data/spec/heroku/client/ssl_endpoint_spec.rb +48 -0
- data/spec/heroku/client_spec.rb +564 -0
- data/spec/heroku/command/addons_spec.rb +585 -0
- data/spec/heroku/command/apps_spec.rb +351 -0
- data/spec/heroku/command/auth_spec.rb +38 -0
- data/spec/heroku/command/base_spec.rb +109 -0
- data/spec/heroku/command/certs_spec.rb +178 -0
- data/spec/heroku/command/config_spec.rb +144 -0
- data/spec/heroku/command/db_spec.rb +110 -0
- data/spec/heroku/command/domains_spec.rb +87 -0
- data/spec/heroku/command/drains_spec.rb +34 -0
- data/spec/heroku/command/git_spec.rb +116 -0
- data/spec/heroku/command/help_spec.rb +93 -0
- data/spec/heroku/command/keys_spec.rb +120 -0
- data/spec/heroku/command/labs_spec.rb +99 -0
- data/spec/heroku/command/logs_spec.rb +60 -0
- data/spec/heroku/command/maintenance_spec.rb +51 -0
- data/spec/heroku/command/pg_spec.rb +223 -0
- data/spec/heroku/command/pgbackups_spec.rb +280 -0
- data/spec/heroku/command/plugins_spec.rb +104 -0
- data/spec/heroku/command/ps_spec.rb +195 -0
- data/spec/heroku/command/releases_spec.rb +130 -0
- data/spec/heroku/command/run_spec.rb +86 -0
- data/spec/heroku/command/sharing_spec.rb +59 -0
- data/spec/heroku/command/ssl_spec.rb +32 -0
- data/spec/heroku/command/stack_spec.rb +46 -0
- data/spec/heroku/command/status_spec.rb +48 -0
- data/spec/heroku/command/version_spec.rb +16 -0
- data/spec/heroku/command_spec.rb +211 -0
- data/spec/heroku/helpers/heroku_postgresql_spec.rb +109 -0
- data/spec/heroku/helpers_spec.rb +48 -0
- data/spec/heroku/plugin_spec.rb +172 -0
- data/spec/heroku/updater_spec.rb +44 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +209 -0
- data/spec/support/display_message_matcher.rb +49 -0
- data/spec/support/openssl_mock_helper.rb +8 -0
- metadata +220 -0
@@ -0,0 +1,23 @@
|
|
1
|
+
require "heroku/command/base"
|
2
|
+
|
3
|
+
# manage heroku account options
|
4
|
+
#
|
5
|
+
class Heroku::Command::Account < Heroku::Command::Base
|
6
|
+
|
7
|
+
# account:confirm_billing
|
8
|
+
#
|
9
|
+
# Confirm that your account can be billed at the end of the month
|
10
|
+
#
|
11
|
+
#Example:
|
12
|
+
#
|
13
|
+
# $ heroku account:confirm_billing
|
14
|
+
# This action will cause your account to be billed at the end of the month
|
15
|
+
# For more information, see http://docs.heroku.com/billing
|
16
|
+
# Are you sure you want to do this? (y/n)
|
17
|
+
#
|
18
|
+
def confirm_billing
|
19
|
+
validate_arguments!
|
20
|
+
Heroku::Helpers.confirm_billing
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
if Heroku::Plugin.list.include?('heroku-accounts')
|
2
|
+
|
3
|
+
require "heroku/command/base"
|
4
|
+
|
5
|
+
# manage multiple heroku accounts
|
6
|
+
#
|
7
|
+
class Heroku::Command::Accounts < Heroku::Command::Base
|
8
|
+
|
9
|
+
# accounts:default
|
10
|
+
# set a system-wide default account
|
11
|
+
def default
|
12
|
+
name = shift_argument
|
13
|
+
validate_arguments!
|
14
|
+
|
15
|
+
unless name
|
16
|
+
error("Please specify an account name.")
|
17
|
+
end
|
18
|
+
|
19
|
+
unless account_exists?(name)
|
20
|
+
error("That account does not exist.")
|
21
|
+
end
|
22
|
+
|
23
|
+
result = %x{ git config --global heroku.account #{name} }
|
24
|
+
|
25
|
+
# update netrc
|
26
|
+
Heroku::Auth.instance_variable_set(:@account, nil) # kill memoization
|
27
|
+
Heroku::Auth.credentials = [Heroku::Auth.user, Heroku::Auth.password]
|
28
|
+
Heroku::Auth.write_credentials
|
29
|
+
|
30
|
+
result
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,305 @@
|
|
1
|
+
require "heroku/command/base"
|
2
|
+
require "heroku/helpers/heroku_postgresql"
|
3
|
+
|
4
|
+
module Heroku::Command
|
5
|
+
|
6
|
+
# manage addon resources
|
7
|
+
#
|
8
|
+
class Addons < Base
|
9
|
+
|
10
|
+
include Heroku::Helpers::HerokuPostgresql
|
11
|
+
|
12
|
+
# addons
|
13
|
+
#
|
14
|
+
# list installed addons
|
15
|
+
#
|
16
|
+
def index
|
17
|
+
validate_arguments!
|
18
|
+
|
19
|
+
installed = api.get_addons(app).body
|
20
|
+
if installed.empty?
|
21
|
+
display("#{app} has no add-ons.")
|
22
|
+
else
|
23
|
+
available, pending = installed.partition { |a| a['configured'] }
|
24
|
+
|
25
|
+
unless available.empty?
|
26
|
+
styled_header("#{app} Configured Add-ons")
|
27
|
+
styled_array(available.map do |a|
|
28
|
+
[a['name'], a['attachment_name'] || '']
|
29
|
+
end)
|
30
|
+
end
|
31
|
+
|
32
|
+
unless pending.empty?
|
33
|
+
styled_header("#{app} Add-ons to Configure")
|
34
|
+
styled_array(pending.map do |a|
|
35
|
+
[a['name'], app_addon_url(a['name'])]
|
36
|
+
end)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# addons:list
|
42
|
+
#
|
43
|
+
# list all available addons
|
44
|
+
#
|
45
|
+
def list
|
46
|
+
addons = heroku.addons
|
47
|
+
if addons.empty?
|
48
|
+
display "No addons available currently"
|
49
|
+
else
|
50
|
+
partitioned_addons = partition_addons(addons)
|
51
|
+
partitioned_addons.each do |key, addons|
|
52
|
+
partitioned_addons[key] = format_for_display(addons)
|
53
|
+
end
|
54
|
+
display_object(partitioned_addons)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# addons:add ADDON
|
59
|
+
#
|
60
|
+
# install an addon
|
61
|
+
#
|
62
|
+
def add
|
63
|
+
configure_addon('Adding') do |addon, config|
|
64
|
+
heroku.install_addon(app, addon, config)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# addons:upgrade ADDON
|
69
|
+
#
|
70
|
+
# upgrade an existing addon
|
71
|
+
#
|
72
|
+
def upgrade
|
73
|
+
configure_addon('Upgrading to') do |addon, config|
|
74
|
+
heroku.upgrade_addon(app, addon, config)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# addons:downgrade ADDON
|
79
|
+
#
|
80
|
+
# downgrade an existing addon
|
81
|
+
#
|
82
|
+
def downgrade
|
83
|
+
configure_addon('Downgrading to') do |addon, config|
|
84
|
+
heroku.upgrade_addon(app, addon, config)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# addons:remove ADDON1 [ADDON2 ...]
|
89
|
+
#
|
90
|
+
# uninstall one or more addons
|
91
|
+
#
|
92
|
+
def remove
|
93
|
+
return unless confirm_command
|
94
|
+
|
95
|
+
args.each do |name|
|
96
|
+
messages = nil
|
97
|
+
action("Removing #{name} on #{app}") do
|
98
|
+
messages = addon_run { heroku.uninstall_addon(app, name, :confirm => app) }
|
99
|
+
end
|
100
|
+
display(messages[:attachment]) if messages[:attachment]
|
101
|
+
display(messages[:message]) if messages[:message]
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# addons:docs ADDON
|
106
|
+
#
|
107
|
+
# open an addon's documentation in your browser
|
108
|
+
#
|
109
|
+
def docs
|
110
|
+
unless addon = shift_argument
|
111
|
+
error("Usage: heroku addons:docs ADDON\nMust specify ADDON to open docs for.")
|
112
|
+
end
|
113
|
+
validate_arguments!
|
114
|
+
|
115
|
+
addon_names = api.get_addons.body.map {|a| a['name']}
|
116
|
+
addon_types = addon_names.map {|name| name.split(':').first}.uniq
|
117
|
+
|
118
|
+
name_matches = addon_names.select {|name| name =~ /^#{addon}/}
|
119
|
+
type_matches = addon_types.select {|name| name =~ /^#{addon}/}
|
120
|
+
|
121
|
+
if name_matches.include?(addon) || type_matches.include?(addon)
|
122
|
+
type_matches = [addon]
|
123
|
+
end
|
124
|
+
|
125
|
+
case type_matches.length
|
126
|
+
when 0 then
|
127
|
+
error([
|
128
|
+
"`#{addon}` is not a heroku add-on.",
|
129
|
+
suggestion(addon, addon_names + addon_types),
|
130
|
+
"See `heroku addons:list` for all available addons."
|
131
|
+
].compact.join("\n"))
|
132
|
+
when 1
|
133
|
+
addon_type = type_matches.first
|
134
|
+
launchy("Opening #{addon_type} docs", addon_docs_url(addon_type))
|
135
|
+
else
|
136
|
+
error("Ambiguous addon name: #{addon}\nPerhaps you meant #{name_matches[0...-1].map {|match| "`#{match}`"}.join(', ')} or `#{name_matches.last}`.\n")
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# addons:open ADDON
|
141
|
+
#
|
142
|
+
# open an addon's dashboard in your browser
|
143
|
+
#
|
144
|
+
def open
|
145
|
+
unless addon = shift_argument
|
146
|
+
error("Usage: heroku addons:open ADDON\nMust specify ADDON to open.")
|
147
|
+
end
|
148
|
+
validate_arguments!
|
149
|
+
|
150
|
+
app_addons = api.get_addons(app).body.map {|a| a['name']}
|
151
|
+
matches = app_addons.select {|a| a =~ /^#{addon}/}.sort
|
152
|
+
|
153
|
+
case matches.length
|
154
|
+
when 0 then
|
155
|
+
addon_names = api.get_addons.body.map {|a| a['name']}
|
156
|
+
if addon_names.any? {|name| name =~ /^#{addon}/}
|
157
|
+
error("Addon not installed: #{addon}")
|
158
|
+
else
|
159
|
+
error([
|
160
|
+
"`#{addon}` is not a heroku add-on.",
|
161
|
+
suggestion(addon, addon_names + addon_names.map {|name| name.split(':').first}.uniq),
|
162
|
+
"See `heroku addons:list` for all available addons."
|
163
|
+
].compact.join("\n"))
|
164
|
+
end
|
165
|
+
when 1 then
|
166
|
+
addon_to_open = matches.first
|
167
|
+
launchy("Opening #{addon_to_open} for #{app}", app_addon_url(addon_to_open))
|
168
|
+
else
|
169
|
+
error("Ambiguous addon name: #{addon}\nPerhaps you meant #{matches[0...-1].map {|match| "`#{match}`"}.join(', ')} or `#{matches.last}`.\n")
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
private
|
174
|
+
|
175
|
+
def addon_docs_url(addon)
|
176
|
+
"https://devcenter.#{heroku.host}/articles/#{addon.split(':').first}"
|
177
|
+
end
|
178
|
+
|
179
|
+
def app_addon_url(addon)
|
180
|
+
"https://api.#{heroku.host}/myapps/#{app}/addons/#{addon}"
|
181
|
+
end
|
182
|
+
|
183
|
+
def partition_addons(addons)
|
184
|
+
addons.group_by{ |a| (a["state"] == "public" ? "available" : a["state"]) }
|
185
|
+
end
|
186
|
+
|
187
|
+
def format_for_display(addons)
|
188
|
+
grouped = addons.inject({}) do |base, addon|
|
189
|
+
group, short = addon['name'].split(':')
|
190
|
+
base[group] ||= []
|
191
|
+
base[group] << addon.merge('short' => short)
|
192
|
+
base
|
193
|
+
end
|
194
|
+
grouped.keys.sort.map do |name|
|
195
|
+
addons = grouped[name]
|
196
|
+
row = name.dup
|
197
|
+
if addons.any? { |a| a['short'] }
|
198
|
+
row << ':'
|
199
|
+
size = row.size
|
200
|
+
stop = false
|
201
|
+
row << addons.map { |a| a['short'] }.compact.sort.map do |short|
|
202
|
+
size += short.size
|
203
|
+
if size < 31
|
204
|
+
short
|
205
|
+
else
|
206
|
+
stop = true
|
207
|
+
nil
|
208
|
+
end
|
209
|
+
end.compact.join(', ')
|
210
|
+
row << '...' if stop
|
211
|
+
end
|
212
|
+
row
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def addon_run
|
217
|
+
response = yield
|
218
|
+
|
219
|
+
if response
|
220
|
+
price = "(#{ response['price'] })" if response['price']
|
221
|
+
|
222
|
+
if response['message'] =~ /(Attached as [A-Z0-9_]+)\n(.*)/m
|
223
|
+
attachment = $1
|
224
|
+
message = $2
|
225
|
+
else
|
226
|
+
attachment = nil
|
227
|
+
message = response['message']
|
228
|
+
end
|
229
|
+
|
230
|
+
begin
|
231
|
+
release = api.get_release(app, 'current').body
|
232
|
+
release = release['name']
|
233
|
+
rescue Heroku::API::Errors::Error
|
234
|
+
release = nil
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
status [ release, price ].compact.join(' ')
|
239
|
+
{ :attachment => attachment, :message => message }
|
240
|
+
rescue RestClient::ResourceNotFound => e
|
241
|
+
error Heroku::Command.extract_error(e.http_body) {
|
242
|
+
e.http_body =~ /^([\w\s]+ not found).?$/ ? $1 : "Resource not found"
|
243
|
+
}
|
244
|
+
rescue RestClient::Locked => ex
|
245
|
+
raise
|
246
|
+
rescue RestClient::RequestFailed => e
|
247
|
+
retry if e.http_code == 402 && confirm_billing
|
248
|
+
error Heroku::Command.extract_error(e.http_body)
|
249
|
+
end
|
250
|
+
|
251
|
+
def configure_addon(label, &install_or_upgrade)
|
252
|
+
addon = args.shift
|
253
|
+
raise CommandFailed.new("Missing add-on name") if addon.nil? || ["--fork", "--follow"].include?(addon)
|
254
|
+
|
255
|
+
config = parse_options(args)
|
256
|
+
config.merge!(:confirm => app) if app == options[:confirm]
|
257
|
+
raise CommandFailed.new("Unexpected arguments: #{args.join(' ')}") unless args.empty?
|
258
|
+
|
259
|
+
hpg_translate_fork_and_follow(addon, config)
|
260
|
+
|
261
|
+
messages = nil
|
262
|
+
action("#{label} #{addon} on #{app}") do
|
263
|
+
messages = addon_run { install_or_upgrade.call(addon, config) }
|
264
|
+
end
|
265
|
+
display(messages[:attachment]) unless messages[:attachment].to_s.strip == ""
|
266
|
+
display(messages[:message]) unless messages[:message].to_s.strip == ""
|
267
|
+
|
268
|
+
display("Use `heroku addons:docs #{addon}` to view documentation.")
|
269
|
+
end
|
270
|
+
|
271
|
+
#this will clean up when we officially deprecate
|
272
|
+
def parse_options(args)
|
273
|
+
config = {}
|
274
|
+
deprecated_args = []
|
275
|
+
flag = /^--/
|
276
|
+
|
277
|
+
args.size.times do
|
278
|
+
break if args.empty?
|
279
|
+
peek = args.first
|
280
|
+
next unless peek && (peek.match(flag) || peek.match(/=/))
|
281
|
+
arg = args.shift
|
282
|
+
peek = args.first
|
283
|
+
key = arg
|
284
|
+
if key.match(/=/)
|
285
|
+
deprecated_args << key unless key.match(flag)
|
286
|
+
key, value = key.split('=', 2)
|
287
|
+
elsif peek.nil? || peek.match(flag)
|
288
|
+
value = true
|
289
|
+
else
|
290
|
+
value = args.shift
|
291
|
+
end
|
292
|
+
value = true if value == 'true'
|
293
|
+
config[key.sub(flag, '')] = value
|
294
|
+
|
295
|
+
if !deprecated_args.empty?
|
296
|
+
out_string = deprecated_args.map{|a| "--#{a}"}.join(' ')
|
297
|
+
display("Warning: non-unix style params have been deprecated, use #{out_string} instead")
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
config
|
302
|
+
end
|
303
|
+
|
304
|
+
end
|
305
|
+
end
|
@@ -0,0 +1,311 @@
|
|
1
|
+
require "heroku/command/base"
|
2
|
+
|
3
|
+
# manage apps (create, destroy)
|
4
|
+
#
|
5
|
+
class Heroku::Command::Apps < Heroku::Command::Base
|
6
|
+
|
7
|
+
# apps
|
8
|
+
#
|
9
|
+
# list your apps
|
10
|
+
#
|
11
|
+
#Example:
|
12
|
+
#
|
13
|
+
# $ heroku apps
|
14
|
+
# === My Apps
|
15
|
+
# myapp1
|
16
|
+
# myapp2
|
17
|
+
#
|
18
|
+
# === Collaborated Apps
|
19
|
+
# theirapp1 other@owner.name
|
20
|
+
#
|
21
|
+
def index
|
22
|
+
validate_arguments!
|
23
|
+
apps = api.get_apps.body
|
24
|
+
unless apps.empty?
|
25
|
+
my_apps, collaborated_apps = apps.partition do |app|
|
26
|
+
app["owner_email"] == Heroku::Auth.user
|
27
|
+
end
|
28
|
+
|
29
|
+
unless my_apps.empty?
|
30
|
+
styled_header("My Apps")
|
31
|
+
styled_array(my_apps.map { |app| app["name"] })
|
32
|
+
end
|
33
|
+
|
34
|
+
unless collaborated_apps.empty?
|
35
|
+
styled_header("Collaborated Apps")
|
36
|
+
styled_array(collaborated_apps.map { |app| [app["name"], app["owner_email"]] })
|
37
|
+
end
|
38
|
+
else
|
39
|
+
display("You have no apps.")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
alias_command "list", "apps"
|
44
|
+
|
45
|
+
# apps:info
|
46
|
+
#
|
47
|
+
# show detailed app information
|
48
|
+
#
|
49
|
+
# -s, --shell # output more shell friendly key/value pairs
|
50
|
+
#
|
51
|
+
#Examples:
|
52
|
+
#
|
53
|
+
# $ heroku apps:info
|
54
|
+
# === myapp
|
55
|
+
# Git URL: git@heroku.com:myapp.git
|
56
|
+
# Repo Size: 5M
|
57
|
+
# ...
|
58
|
+
#
|
59
|
+
# $ heroku apps:info --shell
|
60
|
+
# git_url=git@heroku.com:myapp.git
|
61
|
+
# repo_size=5000000
|
62
|
+
# ...
|
63
|
+
#
|
64
|
+
def info
|
65
|
+
validate_arguments!
|
66
|
+
app_data = api.get_app(app).body
|
67
|
+
|
68
|
+
unless options[:shell]
|
69
|
+
styled_header(app_data["name"])
|
70
|
+
end
|
71
|
+
|
72
|
+
addons_data = api.get_addons(app).body.map {|addon| addon['name']}.sort
|
73
|
+
collaborators_data = api.get_collaborators(app).body.map {|collaborator| collaborator["email"]}.sort
|
74
|
+
collaborators_data.reject! {|email| email == app_data["owner_email"]}
|
75
|
+
|
76
|
+
if options[:shell]
|
77
|
+
if app_data['domain_name']
|
78
|
+
app_data['domain_name'] = app_data['domain_name']['domain']
|
79
|
+
end
|
80
|
+
unless addons_data.empty?
|
81
|
+
app_data['addons'] = addons_data.join(',')
|
82
|
+
end
|
83
|
+
unless collaborators_data.empty?
|
84
|
+
app_data['collaborators'] = collaborators_data.join(',')
|
85
|
+
end
|
86
|
+
app_data.keys.sort_by { |a| a.to_s }.each do |key|
|
87
|
+
hputs("#{key}=#{app_data[key]}")
|
88
|
+
end
|
89
|
+
else
|
90
|
+
data = {}
|
91
|
+
|
92
|
+
unless addons_data.empty?
|
93
|
+
data["Addons"] = addons_data
|
94
|
+
end
|
95
|
+
|
96
|
+
data["Collaborators"] = collaborators_data
|
97
|
+
|
98
|
+
if app_data["create_status"] && app_data["create_status"] != "complete"
|
99
|
+
data["Create Status"] = app_data["create_status"]
|
100
|
+
end
|
101
|
+
|
102
|
+
if app_data["cron_finished_at"]
|
103
|
+
data["Cron Finished At"] = format_date(app_data["cron_finished_at"])
|
104
|
+
end
|
105
|
+
|
106
|
+
if app_data["cron_next_run"]
|
107
|
+
data["Cron Next Run"] = format_date(app_data["cron_next_run"])
|
108
|
+
end
|
109
|
+
|
110
|
+
if app_data["database_size"]
|
111
|
+
data["Database Size"] = format_bytes(app_data["database_size"])
|
112
|
+
end
|
113
|
+
|
114
|
+
data["Git URL"] = app_data["git_url"]
|
115
|
+
|
116
|
+
if app_data["database_tables"]
|
117
|
+
data["Database Size"].gsub!('(empty)', '0K') + " in #{quantify("table", app_data["database_tables"])}"
|
118
|
+
end
|
119
|
+
|
120
|
+
if app_data["dyno_hours"].is_a?(Hash)
|
121
|
+
data["Dyno Hours"] = app_data["dyno_hours"].keys.map do |type|
|
122
|
+
"%s - %0.2f dyno-hours" % [ type.to_s.capitalize, app_data["dyno_hours"][type] ]
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
data["Owner Email"] = app_data["owner_email"]
|
127
|
+
|
128
|
+
if app_data["repo_size"]
|
129
|
+
data["Repo Size"] = format_bytes(app_data["repo_size"])
|
130
|
+
end
|
131
|
+
|
132
|
+
if app_data["slug_size"]
|
133
|
+
data["Slug Size"] = format_bytes(app_data["slug_size"])
|
134
|
+
end
|
135
|
+
|
136
|
+
data["Stack"] = app_data["stack"]
|
137
|
+
if data["Stack"] != "cedar"
|
138
|
+
data.merge!("Dynos" => app_data["dynos"], "Workers" => app_data["workers"])
|
139
|
+
end
|
140
|
+
|
141
|
+
data["Web URL"] = app_data["web_url"]
|
142
|
+
|
143
|
+
styled_hash(data)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
alias_command "info", "apps:info"
|
148
|
+
|
149
|
+
# apps:create [NAME]
|
150
|
+
#
|
151
|
+
# create a new app
|
152
|
+
#
|
153
|
+
# --addons ADDONS # a comma-delimited list of addons to install
|
154
|
+
# -b, --buildpack BUILDPACK # a buildpack url to use for this app
|
155
|
+
# -n, --no-remote # don't create a git remote
|
156
|
+
# -r, --remote REMOTE # the git remote to create, default "heroku"
|
157
|
+
# -s, --stack STACK # the stack on which to create the app
|
158
|
+
#
|
159
|
+
#Examples:
|
160
|
+
#
|
161
|
+
# $ heroku apps:create
|
162
|
+
# Creating floating-dragon-42... done, stack is cedar
|
163
|
+
# http://floating-dragon-42.heroku.com/ | git@heroku.com:floating-dragon-42.git
|
164
|
+
#
|
165
|
+
# $ heroku apps:create -s bamboo
|
166
|
+
# Creating floating-dragon-42... done, stack is bamboo-mri-1.9.2
|
167
|
+
# http://floating-dragon-42.herokuapp.com/ | git@heroku.com:floating-dragon-42.git
|
168
|
+
#
|
169
|
+
# # specify a name
|
170
|
+
# $ heroku apps:create myapp
|
171
|
+
# Creating myapp... done, stack is cedar
|
172
|
+
# http://myapp.heroku.com/ | git@heroku.com:myapp.git
|
173
|
+
#
|
174
|
+
# # create a staging app
|
175
|
+
# $ heroku apps:create myapp-staging --remote staging
|
176
|
+
#
|
177
|
+
def create
|
178
|
+
name = shift_argument || options[:app]
|
179
|
+
validate_arguments!
|
180
|
+
|
181
|
+
info = api.post_app({ "name" => name, "stack" => options[:stack] }).body
|
182
|
+
begin
|
183
|
+
action("Creating #{info['name']}") do
|
184
|
+
if info['create_status'] == 'creating'
|
185
|
+
Timeout::timeout(options[:timeout].to_i) do
|
186
|
+
loop do
|
187
|
+
break if api.get_app(info['name']).body['create_status'] == 'complete'
|
188
|
+
sleep 1
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
status("stack is #{info['stack']}")
|
193
|
+
end
|
194
|
+
|
195
|
+
(options[:addons] || "").split(",").each do |addon|
|
196
|
+
addon.strip!
|
197
|
+
action("Adding #{addon} to #{info["name"]}") do
|
198
|
+
api.post_addon(info["name"], addon)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
if buildpack = options[:buildpack]
|
203
|
+
api.put_config_vars(info["name"], "BUILDPACK_URL" => buildpack)
|
204
|
+
display("BUILDPACK_URL=#{buildpack}")
|
205
|
+
end
|
206
|
+
|
207
|
+
hputs([ info["web_url"], info["git_url"] ].join(" | "))
|
208
|
+
rescue Timeout::Error
|
209
|
+
hputs("Timed Out! Check heroku status for known issues.")
|
210
|
+
end
|
211
|
+
|
212
|
+
unless options[:no_remote].is_a? FalseClass
|
213
|
+
create_git_remote(options[:remote] || "heroku", info["git_url"])
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
alias_command "create", "apps:create"
|
218
|
+
|
219
|
+
# apps:rename NEWNAME
|
220
|
+
#
|
221
|
+
# rename the app
|
222
|
+
#
|
223
|
+
#Example:
|
224
|
+
#
|
225
|
+
# $ heroku apps:rename myapp-newname
|
226
|
+
# http://myapp-newname.herokuapp.com/ | git@heroku.com:myapp-newname.git
|
227
|
+
# Git remote heroku updated
|
228
|
+
#
|
229
|
+
def rename
|
230
|
+
newname = shift_argument
|
231
|
+
if newname.nil? || newname.empty?
|
232
|
+
error("Usage: heroku apps:rename NEWNAME\nMust specify NEWNAME to rename.")
|
233
|
+
end
|
234
|
+
validate_arguments!
|
235
|
+
|
236
|
+
action("Renaming #{app} to #{newname}") do
|
237
|
+
api.put_app(app, "name" => newname)
|
238
|
+
end
|
239
|
+
|
240
|
+
app_data = api.get_app(newname).body
|
241
|
+
hputs([ app_data["web_url"], app_data["git_url"] ].join(" | "))
|
242
|
+
|
243
|
+
if remotes = git_remotes(Dir.pwd)
|
244
|
+
remotes.each do |remote_name, remote_app|
|
245
|
+
next if remote_app != app
|
246
|
+
git "remote rm #{remote_name}"
|
247
|
+
git "remote add #{remote_name} #{app_data["git_url"]}"
|
248
|
+
hputs("Git remote #{remote_name} updated")
|
249
|
+
end
|
250
|
+
else
|
251
|
+
hputs("Don't forget to update your Git remotes on any local checkouts.")
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
alias_command "rename", "apps:rename"
|
256
|
+
|
257
|
+
# apps:open
|
258
|
+
#
|
259
|
+
# open the app in a web browser
|
260
|
+
#
|
261
|
+
#Example:
|
262
|
+
#
|
263
|
+
# $ heroku apps:open
|
264
|
+
# Opening myapp... done
|
265
|
+
#
|
266
|
+
def open
|
267
|
+
validate_arguments!
|
268
|
+
|
269
|
+
app_data = api.get_app(app).body
|
270
|
+
launchy("Opening #{app}", app_data['web_url'])
|
271
|
+
end
|
272
|
+
|
273
|
+
alias_command "open", "apps:open"
|
274
|
+
|
275
|
+
# apps:destroy
|
276
|
+
#
|
277
|
+
# permanently destroy an app
|
278
|
+
#
|
279
|
+
#Example:
|
280
|
+
#
|
281
|
+
# $ heroku apps:destroy -a myapp --confirm myapp
|
282
|
+
# Destroying myapp (including all add-ons)... done
|
283
|
+
#
|
284
|
+
def destroy
|
285
|
+
@app = shift_argument || options[:app] || options[:confirm]
|
286
|
+
validate_arguments!
|
287
|
+
|
288
|
+
unless @app
|
289
|
+
error("Usage: heroku apps:destroy --app APP\nMust specify APP to destroy.")
|
290
|
+
end
|
291
|
+
|
292
|
+
api.get_app(@app) # fail fast if no access or doesn't exist
|
293
|
+
|
294
|
+
message = "WARNING: Potentially Destructive Action\nThis command will destroy #{@app} (including all add-ons)."
|
295
|
+
if confirm_command(@app, message)
|
296
|
+
action("Destroying #{@app} (including all add-ons)") do
|
297
|
+
api.delete_app(@app)
|
298
|
+
if remotes = git_remotes(Dir.pwd)
|
299
|
+
remotes.each do |remote_name, remote_app|
|
300
|
+
next if @app != remote_app
|
301
|
+
git "remote rm #{remote_name}"
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
alias_command "destroy", "apps:destroy"
|
309
|
+
alias_command "apps:delete", "apps:destroy"
|
310
|
+
|
311
|
+
end
|