pogo 2.31.2 → 2.32.14

Sign up to get free protection for your applications and to get access to all the features.
@@ -26,6 +26,8 @@ class Heroku::Command::Pg < Heroku::Command::Base
26
26
 
27
27
  # pg:info [DATABASE]
28
28
  #
29
+ # -x, --extended # Show extended information
30
+ #
29
31
  # Display database information
30
32
  #
31
33
  # If DATABASE is not specified, displays all databases
@@ -35,8 +37,8 @@ class Heroku::Command::Pg < Heroku::Command::Base
35
37
  validate_arguments!
36
38
 
37
39
  if db
38
- name, url = hpg_resolve(db)
39
- display_db name, hpg_info(url)
40
+ attachment = hpg_resolve(db)
41
+ display_db attachment.display_name, hpg_info(attachment, options[:extended])
40
42
  else
41
43
  index
42
44
  end
@@ -52,11 +54,10 @@ class Heroku::Command::Pg < Heroku::Command::Base
52
54
  end
53
55
  validate_arguments!
54
56
 
55
- name, url = hpg_resolve(db)
56
- name ||= 'Custom URL'
57
+ attachment = hpg_resolve(db)
57
58
 
58
- action "Promoting #{name} to DATABASE_URL" do
59
- hpg_promote(url)
59
+ action "Promoting #{attachment.display_name} to DATABASE_URL" do
60
+ hpg_promote(attachment.url)
60
61
  end
61
62
  end
62
63
 
@@ -67,10 +68,10 @@ class Heroku::Command::Pg < Heroku::Command::Base
67
68
  # defaults to DATABASE_URL databases if no DATABASE is specified
68
69
  #
69
70
  def psql
70
- name, url = hpg_resolve(shift_argument, "DATABASE_URL")
71
+ attachment = hpg_resolve(shift_argument, "DATABASE_URL")
71
72
  validate_arguments!
72
73
 
73
- uri = URI.parse(url)
74
+ uri = URI.parse( attachment.url )
74
75
  begin
75
76
  ENV["PGPASSWORD"] = uri.password
76
77
  ENV["PGSSLMODE"] = 'require'
@@ -92,14 +93,14 @@ class Heroku::Command::Pg < Heroku::Command::Base
92
93
  end
93
94
  validate_arguments!
94
95
 
95
- name, url = hpg_resolve(db)
96
+ attachment = hpg_resolve(db) unless db == "SHARED_DATABASE"
96
97
  return unless confirm_command
97
98
 
98
- action("Resetting #{name}") do
99
- if name =~ /^SHARED_DATABASE/i
100
- heroku.database_reset(app)
101
- else
102
- hpg_client(url).reset
99
+ if db == "SHARED_DATABASE"
100
+ action("Resetting SHARED_DATABASE") { heroku.database_reset(app) }
101
+ else
102
+ action("Resetting #{attachment.display_name}") do
103
+ hpg_client(attachment).reset
103
104
  end
104
105
  end
105
106
  end
@@ -114,21 +115,21 @@ class Heroku::Command::Pg < Heroku::Command::Base
114
115
  end
115
116
  validate_arguments!
116
117
 
117
- replica_name, replica_url = hpg_resolve(db)
118
- replica = hpg_info(replica_url)
118
+ replica = hpg_resolve(db)
119
+ replica_info = hpg_info(replica)
119
120
 
120
- unless replica[:following]
121
- error("#{replica_name} is not following another database.")
121
+ unless replica_info[:following]
122
+ error("#{replica.display_name} is not following another database.")
122
123
  end
123
- origin_url = replica[:following]
124
+ origin_url = replica_info[:following]
124
125
  origin_name = database_name_from_url(origin_url)
125
126
 
126
- output_with_bang "#{replica_name} will become writable and no longer"
127
+ output_with_bang "#{replica.display_name} will become writable and no longer"
127
128
  output_with_bang "follow #{origin_name}. This cannot be undone."
128
129
  return unless confirm_command
129
130
 
130
- action "Unfollowing #{db}" do
131
- hpg_client(replica_url).unfollow
131
+ action "Unfollowing #{replica.display_name}" do
132
+ hpg_client(replica).unfollow
132
133
  end
133
134
  end
134
135
 
@@ -143,12 +144,10 @@ class Heroku::Command::Pg < Heroku::Command::Base
143
144
  validate_arguments!
144
145
 
145
146
  if db
146
- wait_for hpg_info(hpg_resolve(db).last)
147
+ wait_for hpg_resolve(db)
147
148
  else
148
- hpg_databases_with_info.keys.sort.each do |name|
149
- unless name =~ /^SHARED_DATABASE/i
150
- wait_for(hpg_databases_with_info[name])
151
- end
149
+ hpg_databases.values.each do |attach|
150
+ wait_for(attach)
152
151
  end
153
152
  end
154
153
  end
@@ -165,23 +164,21 @@ class Heroku::Command::Pg < Heroku::Command::Base
165
164
  end
166
165
  validate_arguments!
167
166
 
168
- name, url = hpg_resolve(db)
169
-
170
- url_is_database_url = (url == app_config_vars["DATABASE_URL"])
167
+ attachment = hpg_resolve(db)
171
168
 
172
169
  if options[:reset]
173
- action "Resetting credentials for #{name}" do
174
- hpg_client(url).rotate_credentials
170
+ action "Resetting credentials for #{attachment.display_name}" do
171
+ hpg_client(attachment).rotate_credentials
175
172
  end
176
- if url_is_database_url
173
+ if attachment.primary_attachment?
177
174
  forget_config!
178
- name, new_url = hpg_resolve(db)
179
- action "Promoting #{name}" do
180
- hpg_promote(new_url)
175
+ attachment = hpg_resolve(db)
176
+ action "Promoting #{attachment.display_name}" do
177
+ hpg_promote(attachment.url)
181
178
  end
182
179
  end
183
180
  else
184
- uri = URI.parse(url)
181
+ uri = URI.parse( attachment.url )
185
182
  display "Connection info string:"
186
183
  display " \"dbname=#{uri.path[1..-1]} host=#{uri.host} port=#{uri.port || 5432} user=#{uri.user} password=#{uri.password} sslmode=require\""
187
184
  end
@@ -200,12 +197,7 @@ private
200
197
  end
201
198
 
202
199
  def display_db(name, db)
203
- pretty_name = name
204
- if !pretty_name.include?(' (DATABASE_URL)') && app_config_vars["#{name}_URL"] == app_config_vars["DATABASE_URL"]
205
- pretty_name += " (DATABASE_URL)"
206
- end
207
-
208
- styled_header(pretty_name)
200
+ styled_header(name)
209
201
  styled_hash(db[:info].inject({}) do |hash, item|
210
202
  hash.update(item["name"] => hpg_info_display(item))
211
203
  end, db[:info].map {|item| item['name']})
@@ -213,31 +205,28 @@ private
213
205
  display
214
206
  end
215
207
 
216
- def hpg_client(url)
217
- Heroku::Client::HerokuPostgresql.new(url)
208
+ def hpg_client(attachment)
209
+ Heroku::Client::HerokuPostgresql.new(attachment)
218
210
  end
219
211
 
220
212
  def hpg_databases_with_info
221
- @hpg_databases_with_info ||= hpg_databases.inject({}) do |hash, (name, url)|
222
- if name =~ /^SHARED_DATABASE/i
223
- data = api.get_app(app).body
224
- hash.update(name => {
225
- :info => [{
226
- 'name' => 'Data Size',
227
- 'values' => [format_bytes(data['database_size'])]
228
- }],
229
- :url => url
230
- })
231
- else
232
- hash.update(name => hpg_info(url))
233
- end
234
- end
213
+ return @hpg_databases_with_info if @hpg_databases_with_info
214
+
215
+ @hpg_databases_with_info = Hash[ hpg_databases.map { |config, att| [att.display_name, hpg_info(att, options[:extended])] } ]
216
+
217
+ return @hpg_databases_with_info
235
218
  end
236
219
 
237
- def hpg_info(url)
238
- info = hpg_client(url).get_database
239
- info[:url] = url
240
- info
220
+ def hpg_info(attachment, extended=false)
221
+ if attachment.resource_name == "SHARED_DATABASE"
222
+ data = api.get_app(app).body
223
+ {:info => [{
224
+ 'name' => 'Data Size',
225
+ 'values' => [format_bytes(data['database_size'])]
226
+ }]}
227
+ else
228
+ hpg_client(attachment).get_database(extended)
229
+ end
241
230
  end
242
231
 
243
232
  def hpg_info_display(item)
@@ -260,13 +249,13 @@ private
260
249
  end
261
250
  end
262
251
 
263
- def wait_for(db)
252
+ def wait_for(attach)
264
253
  ticking do |ticks|
265
- status = hpg_client(db[:url]).get_wait_status
254
+ status = hpg_client(attach).get_wait_status
266
255
  error status[:message] if status[:error?]
267
256
  break if !status[:waiting?] && ticks.zero?
268
257
  redisplay("Waiting for database %s... %s%s" % [
269
- db[:pretty_name],
258
+ attach.display_name,
270
259
  status[:waiting?] ? "#{spinner(ticks)} " : "",
271
260
  status[:message]],
272
261
  !status[:waiting?]) # only display a newline on the last tick
@@ -67,12 +67,14 @@ module Heroku::Command
67
67
  #
68
68
  # if no DATABASE is specified, defaults to DATABASE_URL
69
69
  #
70
- # -e, --expire # if no slots are available to capture, destroy the oldest backup to make room
70
+ # -e, --expire # if no slots are available, destroy the oldest manual backup to make room
71
71
  #
72
72
  def capture
73
- from_name, from_url = hpg_resolve(shift_argument, "DATABASE_URL")
73
+ attachment = hpg_resolve(shift_argument, "DATABASE_URL")
74
74
  validate_arguments!
75
75
 
76
+ from_name = attachment.display_name
77
+ from_url = attachment.url
76
78
  to_url = nil # server will assign
77
79
  to_name = "BACKUP"
78
80
 
@@ -103,13 +105,19 @@ module Heroku::Command
103
105
  #
104
106
  def restore
105
107
  if 0 == args.size
106
- to_name, to_url = hpg_resolve(nil, "DATABASE_URL")
108
+ attachment = hpg_resolve(nil, "DATABASE_URL")
109
+ to_name = attachment.display_name
110
+ to_url = attachment.url
107
111
  backup_id = :latest
108
112
  elsif 1 == args.size
109
- to_name, to_url = hpg_resolve(shift_argument)
113
+ attachment = hpg_resolve(shift_argument)
114
+ to_name = attachment.display_name
115
+ to_url = attachment.url
110
116
  backup_id = :latest
111
117
  else
112
- to_name, to_url = hpg_resolve(shift_argument)
118
+ attachment = hpg_resolve(shift_argument)
119
+ to_name = attachment.display_name
120
+ to_url = attachment.url
113
121
  backup_id = shift_argument
114
122
  end
115
123
 
@@ -195,6 +203,13 @@ module Heroku::Command
195
203
  pgbackup_client.create_transfer(from_url, from_name, to_url, to_name, opts)
196
204
  end
197
205
 
206
+ def poll_error(app)
207
+ error <<-EOM
208
+ Failed to query the PGBackups status API. Your backup may still be running.
209
+ Verify the status of your backup with `heroku pgbackups -a #{app}`
210
+ EOM
211
+ end
212
+
198
213
  def poll_transfer!(transfer)
199
214
  display "\n"
200
215
 
@@ -209,12 +224,17 @@ module Heroku::Command
209
224
  update_display(transfer)
210
225
  break if transfer["finished_at"]
211
226
 
212
- attempts = 0
227
+ sleep_time = 1
213
228
  begin
214
- sleep 1
229
+ sleep(sleep_time)
215
230
  transfer = pgbackup_client.get_transfer(transfer["id"])
216
- rescue RestClient::ServiceUnavailable
217
- (attempts += 1) <= 5 ? retry : raise
231
+ rescue RestClient::ServiceUnavailable, RestClient::ServerBrokeConnection
232
+ if sleep_time > 300
233
+ poll_error(app)
234
+ else
235
+ sleep_time *= 2
236
+ retry
237
+ end
218
238
  end
219
239
  end
220
240
 
@@ -48,7 +48,7 @@ module Heroku::Command
48
48
  exit(1)
49
49
  end
50
50
  else
51
- error("Could not install #{plugin.name}. Please check the URL and try again")
51
+ error("Could not install #{plugin.name}. Please check the URL and try again.")
52
52
  end
53
53
  end
54
54
  end
@@ -125,7 +125,9 @@ protected
125
125
  rendezvous.start
126
126
  rescue Timeout::Error
127
127
  error "\nTimeout awaiting process"
128
- rescue Errno::ECONNREFUSED, Errno::ECONNRESET, OpenSSL::SSL::SSLError
128
+ rescue OpenSSL::SSL::SSLError
129
+ error "Authentication error"
130
+ rescue Errno::ECONNREFUSED, Errno::ECONNRESET
129
131
  error "\nError connecting to process"
130
132
  rescue Interrupt
131
133
  ensure
@@ -38,7 +38,7 @@ class Heroku::Command::Status < Heroku::Command::Base
38
38
  styled_header("#{issue['title']} #{duration}")
39
39
  changes = issue['updates'].map do |issue|
40
40
  [
41
- time_ago(Time.now - Time.parse(issue['created_at'])),
41
+ time_ago(issue['created_at']),
42
42
  issue['update_type'],
43
43
  issue['contents']
44
44
  ]
@@ -0,0 +1,9 @@
1
+ module Excon
2
+
3
+ def self.get_with_redirect(url, options={})
4
+ res = Excon.get(url, options)
5
+ return self.get_with_redirect(res.headers["Location"], options) if res.status == 302
6
+ res
7
+ end
8
+
9
+ end
@@ -25,6 +25,10 @@ module Heroku
25
25
  $stdout.flush
26
26
  end
27
27
  end
28
+
29
+ def host_name
30
+ 'Pogoapp'
31
+ end
28
32
 
29
33
  def redisplay(line, line_break = false)
30
34
  display("\r\e[0K#{line}", line_break)
@@ -5,27 +5,78 @@ module Heroku::Helpers::HerokuPostgresql
5
5
  extend self
6
6
  extend Heroku::Helpers
7
7
 
8
- def app_config_vars
9
- @app_config_vars ||= api.get_config_vars(app).body
8
+ class Attachment
9
+ attr_reader :config_var, :resource_name, :url, :addon, :plan
10
+ def initialize(raw)
11
+ @raw = raw
12
+ @config_var = raw['config_var']
13
+ @resource_name = raw['resource']['name']
14
+ @url = raw['resource']['value']
15
+ @addon, @plan = raw['resource']['type'].split(':')
16
+ end
17
+
18
+ def starter_plan?
19
+ plan =~ /dev|basic/
20
+ end
21
+
22
+ def display_name
23
+ config_var + (primary_attachment? ? " (DATABASE_URL)" : '')
24
+ end
25
+
26
+ def primary_attachment!
27
+ @primary_attachment = true
28
+ end
29
+
30
+ def primary_attachment?
31
+ @primary_attachment
32
+ end
10
33
  end
11
34
 
12
35
  def hpg_addon_name
13
- ENV['HEROKU_POSTGRESQL_ADDON_NAME'] || 'heroku-postgresql'
36
+ if ENV['SHOGUN']
37
+ "shogun-#{ENV['SHOGUN']}"
38
+ else
39
+ ENV['HEROKU_POSTGRESQL_ADDON_NAME'] || 'heroku-postgresql'
40
+ end
14
41
  end
15
42
 
16
- def hpg_addon_prefix
17
- ENV["HEROKU_POSTGRESQL_ADDON_PREFIX"] || "HEROKU_POSTGRESQL"
43
+ def app_config_vars
44
+ @app_config_vars ||= api.get_config_vars(app).body
45
+ end
46
+
47
+ def app_attachments
48
+ @app_attachments ||= api.get_attachments(app).body.map { |raw| Attachment.new(raw) }
18
49
  end
19
50
 
20
51
  def hpg_databases
21
- @hpg_databases ||= app_config_vars.inject({}) do |hash, (name, url)|
22
- if name =~ /^(#{hpg_addon_prefix}\w+)_URL$/
23
- hash.update($1 => url)
24
- elsif name == 'SHARED_DATABASE_URL'
25
- hash.update('SHARED_DATABASE' => url)
26
- end
27
- hash
52
+ return @hpg_databases if @hpg_databases
53
+ pairs = app_attachments.select {|att|
54
+ att.addon == hpg_addon_name
55
+ }.map { |att|
56
+ [att.config_var, att]
57
+ }
58
+ @hpg_databases = Hash[ pairs ]
59
+
60
+ if find_database_url_real_attachment
61
+ @hpg_databases['DATABASE_URL'] = find_database_url_real_attachment
28
62
  end
63
+
64
+ if app_config_vars['SHARED_DATABASE_URL']
65
+ @hpg_databases['SHARED_DATABASE'] = Attachment.new({
66
+ 'config_var' => 'SHARED_DATABASE',
67
+ 'resource' => {
68
+ 'name' => 'SHARED_DATABASE',
69
+ 'value' => app_config_vars['SHARED_DATABASE_URL'],
70
+ 'type' => 'shared:database'
71
+ }
72
+ })
73
+ end
74
+
75
+ return @hpg_databases
76
+ end
77
+
78
+ def resource_url(resource)
79
+ api.get_resource(resource).body['value']
29
80
  end
30
81
 
31
82
  def forget_config!
@@ -33,32 +84,47 @@ module Heroku::Helpers::HerokuPostgresql
33
84
  @app_config_vars = nil
34
85
  end
35
86
 
36
- def hpg_resolve(name, default=nil)
37
- uri = URI.parse(name) rescue nil
38
- if uri && uri.scheme
39
- [nil, name]
87
+ def find_database_url_real_attachment
88
+ primary_db_url = app_config_vars['DATABASE_URL']
89
+ return unless primary_db_url
90
+
91
+ real_config = app_config_vars.detect {|k,v| k != 'DATABASE_URL' && v == primary_db_url }
92
+ if real_config
93
+ real = hpg_databases[real_config.first]
94
+ real.primary_attachment! if real
95
+ return real
40
96
  else
41
- if app_config_vars["DATABASE_URL"]
42
- hpg_databases["DATABASE"] = app_config_vars["DATABASE_URL"]
43
- end
44
- if hpg_databases.empty?
45
- error("Your app has no databases.")
46
- end
97
+ return nil
98
+ end
99
+ end
47
100
 
48
- name = name.to_s.upcase.gsub(/_URL$/, "")
49
-
50
- if hpg_databases[name]
51
- [hpg_pretty_name(name), hpg_databases[name]]
52
- elsif (config_var = "HEROKU_POSTGRESQL_#{name}") && hpg_databases[config_var]
53
- [hpg_pretty_name(config_var), hpg_databases[config_var]]
54
- elsif default && name.empty? && app_config_vars[default]
55
- [hpg_pretty_name(default), app_config_vars[default]]
56
- elsif name.empty?
57
- error("Unknown database. Valid options are: #{hpg_databases.keys.sort.join(", ")}")
58
- else
59
- error("Unknown database: #{name}. Valid options are: #{hpg_databases.keys.sort.join(", ")}")
60
- end
101
+ def match_attachments_by_name(name)
102
+ return [] if name.empty?
103
+ return [name] if hpg_databases[name]
104
+ hpg_databases.keys.grep(%r{#{ name }}i)
105
+ end
106
+
107
+ def hpg_resolve(name, default=nil)
108
+ name = '' if name.nil?
109
+ name = 'DATABASE_URL' if name == 'DATABASE'
110
+
111
+ if hpg_databases.empty?
112
+ error("Your app has no databases.")
61
113
  end
114
+
115
+ found_attachment = nil
116
+ canidates = match_attachments_by_name(name)
117
+ if default && name.empty? && app_config_vars[default]
118
+ found_attachment = hpg_databases[default]
119
+ elsif canidates.size == 1
120
+ found_attachment = hpg_databases[canidates.first]
121
+ end
122
+
123
+ if found_attachment.nil?
124
+ error("Unknown database#{': ' + name unless name.empty?}. Valid options are: #{hpg_databases.keys.sort.join(", ")}")
125
+ end
126
+
127
+ return found_attachment
62
128
  end
63
129
 
64
130
  def hpg_translate_fork_and_follow(addon, config)
@@ -68,35 +134,23 @@ module Heroku::Helpers::HerokuPostgresql
68
134
  unless val.is_a?(String)
69
135
  error("--#{opt} requires a database argument")
70
136
  end
71
- name, url = hpg_resolve(val)
72
- config[opt] = url
73
- end
74
- end
75
- end
76
- end
77
137
 
78
- private
79
-
80
- def hpg_pretty_name(name)
81
- if ['DATABASE', 'DATABASE_URL'].include?(name)
82
- if key = app_config_vars.keys.detect do |key|
83
- if key == 'DATABASE_URL'
84
- next
138
+ uri = URI.parse(val) rescue nil
139
+ if uri && uri.scheme
140
+ argument_url = uri.to_s
85
141
  else
86
- app_config_vars[key] == app_config_vars['DATABASE_URL']
142
+ attachment = hpg_resolve(val)
143
+ argument_url = attachment.url
87
144
  end
145
+
146
+ config[opt] = argument_url
88
147
  end
89
- "#{key.gsub(/_URL$/, '')} (DATABASE_URL)"
90
- else
91
- name.gsub(/_URL$/, '')
92
148
  end
93
- elsif hpg_databases[name] == hpg_databases['DATABASE']
94
- "#{name} (DATABASE_URL)"
95
- else
96
- name
97
149
  end
98
150
  end
99
151
 
152
+ private
153
+
100
154
  def hpg_promote(url)
101
155
  api.put_config_vars(app, "DATABASE_URL" => url)
102
156
  end