pogo 2.31.2
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/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,111 @@
|
|
|
1
|
+
require "heroku/client"
|
|
2
|
+
require "digest/sha2"
|
|
3
|
+
|
|
4
|
+
class Heroku::Client::HerokuPostgresql
|
|
5
|
+
Version = 10
|
|
6
|
+
|
|
7
|
+
include Heroku::Helpers
|
|
8
|
+
|
|
9
|
+
def initialize(url)
|
|
10
|
+
require 'rest_client'
|
|
11
|
+
@heroku_postgresql_host = ENV["HEROKU_POSTGRESQL_HOST"] || "https://shogun.heroku.com"
|
|
12
|
+
@database_sha = sha(url)
|
|
13
|
+
@heroku_postgresql_resource = RestClient::Resource.new(
|
|
14
|
+
"#{@heroku_postgresql_host}/client/v10/databases",
|
|
15
|
+
:headers => { :x_heroku_gem_version => Heroku::Client.version }
|
|
16
|
+
)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def ingress
|
|
20
|
+
http_put "#{@database_sha}/ingress"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def reset
|
|
24
|
+
http_put "#{@database_sha}/reset"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def rotate_credentials
|
|
28
|
+
http_post "#{@database_sha}/credentials_rotation"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def get_database
|
|
32
|
+
http_get @database_sha
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def get_wait_status
|
|
36
|
+
http_get "#{@database_sha}/wait_status"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def unfollow
|
|
40
|
+
http_put "#{@database_sha}/unfollow"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
protected
|
|
44
|
+
|
|
45
|
+
def sha(url)
|
|
46
|
+
Digest::SHA2.hexdigest url
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def sym_keys(c)
|
|
50
|
+
if c.is_a?(Array)
|
|
51
|
+
c.map { |e| sym_keys(e) }
|
|
52
|
+
else
|
|
53
|
+
c.inject({}) do |h, (k, v)|
|
|
54
|
+
h[k.to_sym] = v; h
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def checking_client_version
|
|
60
|
+
begin
|
|
61
|
+
yield
|
|
62
|
+
rescue RestClient::BadRequest => e
|
|
63
|
+
if message = json_decode(e.response.to_s)["upgrade_message"]
|
|
64
|
+
abort(message)
|
|
65
|
+
else
|
|
66
|
+
raise e
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def display_heroku_warning(response)
|
|
72
|
+
warning = response.headers[:x_heroku_warning]
|
|
73
|
+
display warning if warning
|
|
74
|
+
response
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def http_get(path)
|
|
78
|
+
checking_client_version do
|
|
79
|
+
retry_on_exception(RestClient::Exception) do
|
|
80
|
+
response = @heroku_postgresql_resource[path].get
|
|
81
|
+
display_heroku_warning response
|
|
82
|
+
sym_keys(json_decode(response.to_s))
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def http_post(path, payload = {})
|
|
88
|
+
checking_client_version do
|
|
89
|
+
response = @heroku_postgresql_resource[path].post(json_encode(payload))
|
|
90
|
+
display_heroku_warning response
|
|
91
|
+
sym_keys(json_decode(response.to_s))
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def http_put(path, payload = {})
|
|
96
|
+
checking_client_version do
|
|
97
|
+
response = @heroku_postgresql_resource[path].put(json_encode(payload))
|
|
98
|
+
display_heroku_warning response
|
|
99
|
+
sym_keys(json_decode(response.to_s))
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
module HerokuPostgresql
|
|
105
|
+
class Client < Heroku::Client::HerokuPostgresql
|
|
106
|
+
def initialize(*args)
|
|
107
|
+
Heroku::Helpers.deprecate "HerokuPostgresql::Client has been deprecated. Please use Heroku::Client::HerokuPostgresql instead."
|
|
108
|
+
super
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
require "heroku/client"
|
|
2
|
+
|
|
3
|
+
class Heroku::Client::Pgbackups
|
|
4
|
+
|
|
5
|
+
include Heroku::Helpers
|
|
6
|
+
|
|
7
|
+
def initialize(uri)
|
|
8
|
+
require 'rest_client'
|
|
9
|
+
@uri = URI.parse(uri)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def authenticated_resource(path)
|
|
13
|
+
host = "#{@uri.scheme}://#{@uri.host}"
|
|
14
|
+
host += ":#{@uri.port}" if @uri.port
|
|
15
|
+
RestClient::Resource.new("#{host}#{path}",
|
|
16
|
+
:user => @uri.user,
|
|
17
|
+
:password => @uri.password,
|
|
18
|
+
:headers => {:x_heroku_gem_version => Heroku::Client.version}
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def create_transfer(from_url, from_name, to_url, to_name, opts={})
|
|
23
|
+
# opts[:expire] => true will delete the oldest backup if at the plan limit
|
|
24
|
+
resource = authenticated_resource("/client/transfers")
|
|
25
|
+
params = {:from_url => from_url, :from_name => from_name, :to_url => to_url, :to_name => to_name}.merge opts
|
|
26
|
+
json_decode post(resource, params).body
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def get_transfers
|
|
30
|
+
resource = authenticated_resource("/client/transfers")
|
|
31
|
+
json_decode get(resource).body
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def get_transfer(id)
|
|
35
|
+
resource = authenticated_resource("/client/transfers/#{id}")
|
|
36
|
+
json_decode get(resource).body
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def get_backups(opts={})
|
|
40
|
+
resource = authenticated_resource("/client/backups")
|
|
41
|
+
json_decode get(resource).body
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def get_backup(name, opts={})
|
|
45
|
+
name = URI.escape(name)
|
|
46
|
+
resource = authenticated_resource("/client/backups/#{name}")
|
|
47
|
+
json_decode get(resource).body
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def get_latest_backup
|
|
51
|
+
resource = authenticated_resource("/client/latest_backup")
|
|
52
|
+
json_decode get(resource).body
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def delete_backup(name)
|
|
56
|
+
name = URI.escape(name)
|
|
57
|
+
begin
|
|
58
|
+
resource = authenticated_resource("/client/backups/#{name}")
|
|
59
|
+
delete(resource).body
|
|
60
|
+
true
|
|
61
|
+
rescue RestClient::ResourceNotFound => e
|
|
62
|
+
false
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def get(resource)
|
|
69
|
+
check_errors do
|
|
70
|
+
response = resource.get
|
|
71
|
+
display_heroku_warning response
|
|
72
|
+
response
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def post(resource, params)
|
|
77
|
+
check_errors do
|
|
78
|
+
response = resource.post(params)
|
|
79
|
+
display_heroku_warning response
|
|
80
|
+
response
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def delete(resource)
|
|
85
|
+
check_errors do
|
|
86
|
+
response = resource.delete
|
|
87
|
+
display_heroku_warning response
|
|
88
|
+
response
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def check_errors
|
|
93
|
+
yield
|
|
94
|
+
rescue RestClient::Unauthorized
|
|
95
|
+
error "Invalid PGBACKUPS_URL"
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def display_heroku_warning(response)
|
|
99
|
+
warning = response.headers[:x_heroku_warning]
|
|
100
|
+
display warning if warning
|
|
101
|
+
response
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
module Pgbackups
|
|
107
|
+
class Client < Heroku::Client::Pgbackups
|
|
108
|
+
def initialize(*args)
|
|
109
|
+
Heroku::Helpers.deprecate "Pgbackups::Client has been deprecated. Please use Heroku::Client::Pgbackups instead."
|
|
110
|
+
super
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
require "timeout"
|
|
2
|
+
require "socket"
|
|
3
|
+
require "uri"
|
|
4
|
+
require "heroku/client"
|
|
5
|
+
require "heroku/helpers"
|
|
6
|
+
|
|
7
|
+
class Heroku::Client::Rendezvous
|
|
8
|
+
|
|
9
|
+
include Heroku::Helpers
|
|
10
|
+
|
|
11
|
+
attr_reader :rendezvous_url, :connect_timeout, :activity_timeout, :input, :output, :on_connect
|
|
12
|
+
|
|
13
|
+
def initialize(opts)
|
|
14
|
+
@rendezvous_url = opts[:rendezvous_url]
|
|
15
|
+
@connect_timeout = opts[:connect_timeout]
|
|
16
|
+
@activity_timeout = opts[:activity_timeout]
|
|
17
|
+
@input = opts[:input]
|
|
18
|
+
@output = opts[:output]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def on_connect(&blk)
|
|
22
|
+
@on_connect = blk if block_given?
|
|
23
|
+
@on_connect
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def start
|
|
27
|
+
uri = URI.parse(rendezvous_url)
|
|
28
|
+
host, port, secret = uri.host, uri.port, uri.path[1..-1]
|
|
29
|
+
|
|
30
|
+
ssl_socket = Timeout.timeout(connect_timeout) do
|
|
31
|
+
ssl_context = OpenSSL::SSL::SSLContext.new
|
|
32
|
+
if ((host =~ /heroku\.com$/) && !(ENV["HEROKU_SSL_VERIFY"] == "disable"))
|
|
33
|
+
ssl_context.ca_file = File.expand_path("../../../../data/cacert.pem", __FILE__)
|
|
34
|
+
ssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
|
35
|
+
end
|
|
36
|
+
tcp_socket = TCPSocket.open(host, port)
|
|
37
|
+
ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, ssl_context)
|
|
38
|
+
ssl_socket.connect
|
|
39
|
+
ssl_socket.puts(secret)
|
|
40
|
+
ssl_socket.readline
|
|
41
|
+
ssl_socket
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
on_connect.call if on_connect
|
|
45
|
+
|
|
46
|
+
readables = [input, ssl_socket].compact
|
|
47
|
+
|
|
48
|
+
begin
|
|
49
|
+
loop do
|
|
50
|
+
if o = IO.select(readables, nil, nil, activity_timeout)
|
|
51
|
+
if (input && (o.first.first == input))
|
|
52
|
+
begin
|
|
53
|
+
data = input.readpartial(10000)
|
|
54
|
+
rescue EOFError
|
|
55
|
+
readables.delete(input)
|
|
56
|
+
next
|
|
57
|
+
end
|
|
58
|
+
if running_on_windows?
|
|
59
|
+
data.gsub!("\r\n", "\n") # prevent double CRs
|
|
60
|
+
end
|
|
61
|
+
ssl_socket.write(data)
|
|
62
|
+
ssl_socket.flush
|
|
63
|
+
elsif (o.first.first == ssl_socket)
|
|
64
|
+
begin
|
|
65
|
+
data = ssl_socket.readpartial(10000)
|
|
66
|
+
rescue EOFError
|
|
67
|
+
break
|
|
68
|
+
end
|
|
69
|
+
output.write(fixup(data))
|
|
70
|
+
end
|
|
71
|
+
else
|
|
72
|
+
raise(Timeout::Error.new)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
rescue Interrupt
|
|
76
|
+
ssl_socket.write(3.chr)
|
|
77
|
+
ssl_socket.flush
|
|
78
|
+
retry
|
|
79
|
+
rescue SignalException => e
|
|
80
|
+
if Signal.list["QUIT"] == e.signo
|
|
81
|
+
ssl_socket.write(28.chr)
|
|
82
|
+
ssl_socket.flush
|
|
83
|
+
retry
|
|
84
|
+
end
|
|
85
|
+
raise
|
|
86
|
+
rescue Errno::EIO
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
|
|
92
|
+
def fixup(data)
|
|
93
|
+
return nil if ! data
|
|
94
|
+
if data.respond_to?(:force_encoding)
|
|
95
|
+
data.force_encoding('utf-8') if data.respond_to?(:force_encoding)
|
|
96
|
+
end
|
|
97
|
+
if running_on_windows?
|
|
98
|
+
begin
|
|
99
|
+
data.gsub!(/\e\[[\d;]+m/, '')
|
|
100
|
+
rescue # ignore failed gsub, for instance when non-utf8
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
output.isatty ? data : data.gsub(/\cM/,"")
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
class Heroku::Client
|
|
2
|
+
def ssl_endpoint_add(app, pem, key)
|
|
3
|
+
json_decode(post("apps/#{app}/ssl-endpoints", :accept => :json, :pem => pem, :key => key).to_s)
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
def ssl_endpoint_info(app, cname)
|
|
7
|
+
json_decode(get("apps/#{app}/ssl-endpoints/#{escape(cname)}", :accept => :json).to_s)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def ssl_endpoint_list(app)
|
|
11
|
+
json_decode(get("apps/#{app}/ssl-endpoints", :accept => :json).to_s)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def ssl_endpoint_remove(app, cname)
|
|
15
|
+
json_decode(delete("apps/#{app}/ssl-endpoints/#{escape(cname)}", :accept => :json).to_s)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def ssl_endpoint_rollback(app, cname)
|
|
19
|
+
json_decode(post("apps/#{app}/ssl-endpoints/#{escape(cname)}/rollback", :accept => :json).to_s)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def ssl_endpoint_update(app, cname, pem, key)
|
|
23
|
+
json_decode(put("apps/#{app}/ssl-endpoints/#{escape(cname)}", :accept => :json, :pem => pem, :key => key).to_s)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
require 'heroku/helpers'
|
|
2
|
+
require 'heroku/plugin'
|
|
3
|
+
require 'heroku/version'
|
|
4
|
+
require "optparse"
|
|
5
|
+
|
|
6
|
+
module Heroku
|
|
7
|
+
module Command
|
|
8
|
+
class CommandFailed < RuntimeError; end
|
|
9
|
+
|
|
10
|
+
extend Heroku::Helpers
|
|
11
|
+
|
|
12
|
+
def self.load
|
|
13
|
+
Dir[File.join(File.dirname(__FILE__), "command", "*.rb")].each do |file|
|
|
14
|
+
require file
|
|
15
|
+
end
|
|
16
|
+
Heroku::Plugin.load!
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.commands
|
|
20
|
+
@@commands ||= {}
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.command_aliases
|
|
24
|
+
@@command_aliases ||= {}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.files
|
|
28
|
+
@@files ||= Hash.new {|hash,key| hash[key] = File.readlines(key).map {|line| line.strip}}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.namespaces
|
|
32
|
+
@@namespaces ||= {}
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.register_command(command)
|
|
36
|
+
commands[command[:command]] = command
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.register_namespace(namespace)
|
|
40
|
+
namespaces[namespace[:name]] = namespace
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.current_command
|
|
44
|
+
@current_command
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def self.current_command=(new_current_command)
|
|
48
|
+
@current_command = new_current_command
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def self.current_args
|
|
52
|
+
@current_args
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def self.current_options
|
|
56
|
+
@current_options ||= {}
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def self.global_options
|
|
60
|
+
@global_options ||= []
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def self.invalid_arguments
|
|
64
|
+
@invalid_arguments
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def self.shift_argument
|
|
68
|
+
# dup argument to get a non-frozen string
|
|
69
|
+
@invalid_arguments.shift.dup rescue nil
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def self.validate_arguments!
|
|
73
|
+
unless invalid_arguments.empty?
|
|
74
|
+
arguments = invalid_arguments.map {|arg| "\"#{arg}\""}
|
|
75
|
+
if arguments.length == 1
|
|
76
|
+
message = "Invalid argument: #{arguments.first}"
|
|
77
|
+
elsif arguments.length > 1
|
|
78
|
+
message = "Invalid arguments: "
|
|
79
|
+
message << arguments[0...-1].join(", ")
|
|
80
|
+
message << " and "
|
|
81
|
+
message << arguments[-1]
|
|
82
|
+
end
|
|
83
|
+
$stderr.puts(format_with_bang(message))
|
|
84
|
+
run(current_command, ["--help"])
|
|
85
|
+
exit(1)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def self.warnings
|
|
90
|
+
@warnings ||= []
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def self.display_warnings
|
|
94
|
+
unless warnings.empty?
|
|
95
|
+
$stderr.puts(warnings.map {|warning| " ! #{warning}"}.join("\n"))
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def self.global_option(name, *args, &blk)
|
|
100
|
+
# args.sort.reverse gives -l, --long order
|
|
101
|
+
global_options << { :name => name.to_s, :args => args.sort.reverse, :proc => blk }
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
global_option :app, "-a", "--app APP" do |app|
|
|
105
|
+
raise OptionParser::InvalidOption.new(app) if app == "pp"
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
global_option :confirm, "--confirm APP"
|
|
109
|
+
global_option :help, "-h", "--help"
|
|
110
|
+
global_option :remote, "--remote REMOTE"
|
|
111
|
+
|
|
112
|
+
def self.prepare_run(cmd, args=[])
|
|
113
|
+
command = parse(cmd)
|
|
114
|
+
|
|
115
|
+
if args.include?('-h') || args.include?('--help')
|
|
116
|
+
args.unshift(cmd) unless cmd =~ /^-.*/
|
|
117
|
+
cmd = 'help'
|
|
118
|
+
command = parse('help')
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
unless command
|
|
122
|
+
if cmd == '--version'
|
|
123
|
+
cmd = 'version'
|
|
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
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
@current_command = cmd
|
|
135
|
+
@anonymized_args, @normalized_args = [], []
|
|
136
|
+
|
|
137
|
+
opts = {}
|
|
138
|
+
invalid_options = []
|
|
139
|
+
|
|
140
|
+
parser = OptionParser.new do |parser|
|
|
141
|
+
# remove OptionParsers Officious['version'] to avoid conflicts
|
|
142
|
+
# see: https://github.com/ruby/ruby/blob/trunk/lib/optparse.rb#L814
|
|
143
|
+
parser.base.long.delete('version')
|
|
144
|
+
(global_options + command[:options]).each do |option|
|
|
145
|
+
parser.on(*option[:args]) do |value|
|
|
146
|
+
if option[:proc]
|
|
147
|
+
option[:proc].call(value)
|
|
148
|
+
end
|
|
149
|
+
opts[option[:name].gsub('-', '_').to_sym] = value
|
|
150
|
+
ARGV.join(' ') =~ /(#{option[:args].map {|arg| arg.split(' ', 2).first}.join('|')})/
|
|
151
|
+
@anonymized_args << "#{$1} _"
|
|
152
|
+
@normalized_args << "#{option[:args].last.split(' ', 2).first} _"
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
begin
|
|
158
|
+
parser.order!(args) do |nonopt|
|
|
159
|
+
invalid_options << nonopt
|
|
160
|
+
@anonymized_args << '!'
|
|
161
|
+
@normalized_args << '!'
|
|
162
|
+
end
|
|
163
|
+
rescue OptionParser::InvalidOption => ex
|
|
164
|
+
invalid_options << ex.args.first
|
|
165
|
+
@anonymized_args << '!'
|
|
166
|
+
@normalized_args << '!'
|
|
167
|
+
retry
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
args.concat(invalid_options)
|
|
171
|
+
|
|
172
|
+
@current_args = args
|
|
173
|
+
@current_options = opts
|
|
174
|
+
@invalid_arguments = invalid_options
|
|
175
|
+
|
|
176
|
+
command_instance = command[:klass].new(args.dup, opts.dup)
|
|
177
|
+
|
|
178
|
+
@anonymous_command = [ARGV.first, *@anonymized_args].join(' ')
|
|
179
|
+
|
|
180
|
+
if !@normalized_args.include?('--app _') && (implied_app = command_instance.app rescue nil)
|
|
181
|
+
@normalized_args << '--app _'
|
|
182
|
+
end
|
|
183
|
+
@normalized_command = [ARGV.first, @normalized_args.sort_by {|arg| arg.gsub('-', '')}].join(' ')
|
|
184
|
+
|
|
185
|
+
[ command_instance, command[:method] ]
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def self.run(cmd, arguments=[])
|
|
189
|
+
begin
|
|
190
|
+
object, method = prepare_run(cmd, arguments.dup)
|
|
191
|
+
object.send(method)
|
|
192
|
+
rescue Interrupt, StandardError, SystemExit => error
|
|
193
|
+
# load likely error classes, as they may not be loaded yet due to defered loads
|
|
194
|
+
require 'heroku-api'
|
|
195
|
+
require 'rest_client'
|
|
196
|
+
raise(error)
|
|
197
|
+
end
|
|
198
|
+
rescue Heroku::API::Errors::Unauthorized, RestClient::Unauthorized
|
|
199
|
+
puts "Authentication failure"
|
|
200
|
+
unless ENV['HEROKU_API_KEY']
|
|
201
|
+
run "login"
|
|
202
|
+
retry
|
|
203
|
+
end
|
|
204
|
+
rescue Heroku::API::Errors::VerificationRequired, RestClient::PaymentRequired => e
|
|
205
|
+
retry if Heroku::Helpers.confirm_billing
|
|
206
|
+
rescue Heroku::API::Errors::NotFound => e
|
|
207
|
+
error extract_error(e.response.body) {
|
|
208
|
+
e.response.body =~ /^([\w\s]+ not found).?$/ ? $1 : "Resource not found"
|
|
209
|
+
}
|
|
210
|
+
rescue RestClient::ResourceNotFound => e
|
|
211
|
+
error extract_error(e.http_body) {
|
|
212
|
+
e.http_body =~ /^([\w\s]+ not found).?$/ ? $1 : "Resource not found"
|
|
213
|
+
}
|
|
214
|
+
rescue Heroku::API::Errors::Locked => e
|
|
215
|
+
app = e.response.headers[:x_confirmation_required]
|
|
216
|
+
if confirm_command(app, extract_error(e.response.body))
|
|
217
|
+
arguments << '--confirm' << app
|
|
218
|
+
retry
|
|
219
|
+
end
|
|
220
|
+
rescue RestClient::Locked => e
|
|
221
|
+
app = e.response.headers[:x_confirmation_required]
|
|
222
|
+
if confirm_command(app, extract_error(e.http_body))
|
|
223
|
+
arguments << '--confirm' << app
|
|
224
|
+
retry
|
|
225
|
+
end
|
|
226
|
+
rescue Heroku::API::Errors::Timeout, RestClient::RequestTimeout
|
|
227
|
+
error "API request timed out. Please try again, or contact support@heroku.com if this issue persists."
|
|
228
|
+
rescue Heroku::API::Errors::ErrorWithResponse => e
|
|
229
|
+
error extract_error(e.response.body)
|
|
230
|
+
rescue RestClient::RequestFailed => e
|
|
231
|
+
error extract_error(e.http_body)
|
|
232
|
+
rescue CommandFailed => e
|
|
233
|
+
error e.message
|
|
234
|
+
rescue OptionParser::ParseError
|
|
235
|
+
commands[cmd] ? run("help", [cmd]) : run("help")
|
|
236
|
+
ensure
|
|
237
|
+
display_warnings
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def self.parse(cmd)
|
|
241
|
+
commands[cmd] || commands[command_aliases[cmd]]
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def self.extract_error(body, options={})
|
|
245
|
+
default_error = block_given? ? yield : "Internal server error.\nRun 'heroku status' to check for known platform issues."
|
|
246
|
+
parse_error_xml(body) || parse_error_json(body) || parse_error_plain(body) || default_error
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def self.parse_error_xml(body)
|
|
250
|
+
xml_errors = REXML::Document.new(body).elements.to_a("//errors/error")
|
|
251
|
+
msg = xml_errors.map { |a| a.text }.join(" / ")
|
|
252
|
+
return msg unless msg.empty?
|
|
253
|
+
rescue Exception
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def self.parse_error_json(body)
|
|
257
|
+
json = json_decode(body.to_s) rescue false
|
|
258
|
+
case json
|
|
259
|
+
when Array
|
|
260
|
+
json.first.join(' ') # message like [['base', 'message']]
|
|
261
|
+
when Hash
|
|
262
|
+
json['error'] # message like {'error' => 'message'}
|
|
263
|
+
else
|
|
264
|
+
nil
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def self.parse_error_plain(body)
|
|
269
|
+
return unless body.respond_to?(:headers) && body.headers[:content_type].to_s.include?("text/plain")
|
|
270
|
+
body.to_s
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
end
|