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,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
|