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