pogo 2.31.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. data/README.md +73 -0
  2. data/bin/pogo +22 -0
  3. data/data/cacert.pem +3988 -0
  4. data/lib/heroku.rb +22 -0
  5. data/lib/heroku/auth.rb +320 -0
  6. data/lib/heroku/cli.rb +38 -0
  7. data/lib/heroku/client.rb +764 -0
  8. data/lib/heroku/client/heroku_postgresql.rb +111 -0
  9. data/lib/heroku/client/pgbackups.rb +113 -0
  10. data/lib/heroku/client/rendezvous.rb +105 -0
  11. data/lib/heroku/client/ssl_endpoint.rb +25 -0
  12. data/lib/heroku/command.rb +273 -0
  13. data/lib/heroku/command/account.rb +23 -0
  14. data/lib/heroku/command/accounts.rb +34 -0
  15. data/lib/heroku/command/addons.rb +305 -0
  16. data/lib/heroku/command/apps.rb +311 -0
  17. data/lib/heroku/command/auth.rb +86 -0
  18. data/lib/heroku/command/base.rb +230 -0
  19. data/lib/heroku/command/certs.rb +148 -0
  20. data/lib/heroku/command/config.rb +137 -0
  21. data/lib/heroku/command/db.rb +218 -0
  22. data/lib/heroku/command/domains.rb +85 -0
  23. data/lib/heroku/command/drains.rb +46 -0
  24. data/lib/heroku/command/git.rb +65 -0
  25. data/lib/heroku/command/help.rb +163 -0
  26. data/lib/heroku/command/keys.rb +115 -0
  27. data/lib/heroku/command/labs.rb +161 -0
  28. data/lib/heroku/command/logs.rb +98 -0
  29. data/lib/heroku/command/maintenance.rb +61 -0
  30. data/lib/heroku/command/pg.rb +277 -0
  31. data/lib/heroku/command/pgbackups.rb +289 -0
  32. data/lib/heroku/command/plugins.rb +110 -0
  33. data/lib/heroku/command/ps.rb +232 -0
  34. data/lib/heroku/command/releases.rb +124 -0
  35. data/lib/heroku/command/run.rb +179 -0
  36. data/lib/heroku/command/sharing.rb +89 -0
  37. data/lib/heroku/command/ssl.rb +61 -0
  38. data/lib/heroku/command/stack.rb +62 -0
  39. data/lib/heroku/command/status.rb +51 -0
  40. data/lib/heroku/command/update.rb +47 -0
  41. data/lib/heroku/command/version.rb +23 -0
  42. data/lib/heroku/deprecated.rb +5 -0
  43. data/lib/heroku/deprecated/help.rb +38 -0
  44. data/lib/heroku/distribution.rb +9 -0
  45. data/lib/heroku/helpers.rb +517 -0
  46. data/lib/heroku/helpers/heroku_postgresql.rb +104 -0
  47. data/lib/heroku/plugin.rb +161 -0
  48. data/lib/heroku/updater.rb +158 -0
  49. data/lib/heroku/version.rb +3 -0
  50. data/lib/vendor/heroku/okjson.rb +598 -0
  51. data/spec/helper/legacy_help.rb +16 -0
  52. data/spec/heroku/auth_spec.rb +246 -0
  53. data/spec/heroku/client/heroku_postgresql_spec.rb +34 -0
  54. data/spec/heroku/client/pgbackups_spec.rb +43 -0
  55. data/spec/heroku/client/rendezvous_spec.rb +62 -0
  56. data/spec/heroku/client/ssl_endpoint_spec.rb +48 -0
  57. data/spec/heroku/client_spec.rb +564 -0
  58. data/spec/heroku/command/addons_spec.rb +585 -0
  59. data/spec/heroku/command/apps_spec.rb +351 -0
  60. data/spec/heroku/command/auth_spec.rb +38 -0
  61. data/spec/heroku/command/base_spec.rb +109 -0
  62. data/spec/heroku/command/certs_spec.rb +178 -0
  63. data/spec/heroku/command/config_spec.rb +144 -0
  64. data/spec/heroku/command/db_spec.rb +110 -0
  65. data/spec/heroku/command/domains_spec.rb +87 -0
  66. data/spec/heroku/command/drains_spec.rb +34 -0
  67. data/spec/heroku/command/git_spec.rb +116 -0
  68. data/spec/heroku/command/help_spec.rb +93 -0
  69. data/spec/heroku/command/keys_spec.rb +120 -0
  70. data/spec/heroku/command/labs_spec.rb +99 -0
  71. data/spec/heroku/command/logs_spec.rb +60 -0
  72. data/spec/heroku/command/maintenance_spec.rb +51 -0
  73. data/spec/heroku/command/pg_spec.rb +223 -0
  74. data/spec/heroku/command/pgbackups_spec.rb +280 -0
  75. data/spec/heroku/command/plugins_spec.rb +104 -0
  76. data/spec/heroku/command/ps_spec.rb +195 -0
  77. data/spec/heroku/command/releases_spec.rb +130 -0
  78. data/spec/heroku/command/run_spec.rb +86 -0
  79. data/spec/heroku/command/sharing_spec.rb +59 -0
  80. data/spec/heroku/command/ssl_spec.rb +32 -0
  81. data/spec/heroku/command/stack_spec.rb +46 -0
  82. data/spec/heroku/command/status_spec.rb +48 -0
  83. data/spec/heroku/command/version_spec.rb +16 -0
  84. data/spec/heroku/command_spec.rb +211 -0
  85. data/spec/heroku/helpers/heroku_postgresql_spec.rb +109 -0
  86. data/spec/heroku/helpers_spec.rb +48 -0
  87. data/spec/heroku/plugin_spec.rb +172 -0
  88. data/spec/heroku/updater_spec.rb +44 -0
  89. data/spec/spec.opts +1 -0
  90. data/spec/spec_helper.rb +209 -0
  91. data/spec/support/display_message_matcher.rb +49 -0
  92. data/spec/support/openssl_mock_helper.rb +8 -0
  93. 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