pogo 2.31.2 → 2.32.14

Sign up to get free protection for your applications and to get access to all the features.
data/bin/pogo CHANGED
@@ -2,10 +2,10 @@
2
2
  # encoding: UTF-8
3
3
 
4
4
  # pogo defaults
5
- ENV['HEROKU_HOST'] ||= 'http://api.pogoapp.com'
6
- ENV['HEROKU_STATUS_HOST'] ||= 'http://status.pogoapp.com'
7
- ENV['HEROKU_SSL_VERIFY'] ||= 'disable'
8
- ENV['HEROKU_GIT_HOST'] ||= 'git-i.pogoapp.com'
5
+ ENV['HEROKU_HOST'] ||= 'pogoapp.com'
6
+ ENV['HEROKU_STATUS_HOST'] ||= 'status.pogoapp.com'
7
+ ENV['HEROKU_GIT_HOST'] ||= 'git.pogoapp.com'
8
+ ENV['HEROKU_SSL_VERIFY'] ||= 'disable' # StartCom cert + RestClient = :(
9
9
 
10
10
  # resolve bin path, ignoring symlinks
11
11
  require "pathname"
@@ -19,4 +19,5 @@ Heroku::Updater.disable("`heroku update` is only available from Heroku Toolbelt.
19
19
 
20
20
  # start up the CLI
21
21
  require "heroku/cli"
22
+ Heroku.user_agent = "heroku-gem/#{Heroku::VERSION} (#{RUBY_PLATFORM}) ruby/#{RUBY_VERSION}"
22
23
  Heroku::CLI.start(*ARGV)
data/lib/heroku.rb CHANGED
@@ -4,19 +4,14 @@ require "heroku/version"
4
4
 
5
5
  module Heroku
6
6
 
7
- USER_AGENT = 'legacy' # left for backwards compatibility with old toolbelt bin files
7
+ USER_AGENT = "heroku-gem/#{Heroku::VERSION} (#{RUBY_PLATFORM}) ruby/#{RUBY_VERSION}"
8
8
 
9
9
  def self.user_agent
10
- type = if ENV['GEM_HOME'] && __FILE__.include?(ENV['GEM_HOME'])
11
- 'gem'
12
- else
13
- 'toolbelt'
14
- end
15
- user_agent = "heroku-#{type}/#{Heroku::Updater.latest_local_version} (#{RUBY_PLATFORM}) ruby/#{RUBY_VERSION}"
16
- if Heroku::Updater.autoupdate?
17
- user_agent << ' autoupdate'
18
- end
19
- user_agent
10
+ @@user_agent ||= USER_AGENT
11
+ end
12
+
13
+ def self.user_agent=(agent)
14
+ @@user_agent = agent
20
15
  end
21
16
 
22
17
  end
data/lib/heroku/auth.rb CHANGED
@@ -178,7 +178,7 @@ class Heroku::Auth
178
178
  end
179
179
 
180
180
  def ask_for_credentials
181
- puts "Enter your Heroku credentials."
181
+ puts "Enter your #{host_name} credentials."
182
182
 
183
183
  print "Email: "
184
184
  user = ask
@@ -300,12 +300,29 @@ class Heroku::Auth
300
300
  @login_attempts < 3
301
301
  end
302
302
 
303
+ def verified_hosts
304
+ %w( heroku.com heroku-shadow.com )
305
+ end
306
+
307
+ def base_host(host)
308
+ URI.parse(full_host(host)).host.split(".")[-2..-1].join(".")
309
+ end
310
+
311
+ def full_host(host)
312
+ (host =~ /^http/) ? host : "https://api.#{host}"
313
+ end
314
+
315
+ def verify_host?(host)
316
+ hostname = base_host(host)
317
+ verified = verified_hosts.include?(hostname)
318
+ verified = false if ENV["HEROKU_SSL_VERIFY"] == "disable"
319
+ verified
320
+ end
321
+
303
322
  protected
304
323
 
305
324
  def default_params
306
- full_host = (host =~ /^http/) ? host : "https://api.#{host}"
307
- verify_ssl = ENV['HEROKU_SSL_VERIFY'] != 'disable' && full_host =~ %r|^https://api.heroku.com|
308
- uri = URI(full_host)
325
+ uri = URI.parse(full_host(host))
309
326
  {
310
327
  :headers => {
311
328
  'User-Agent' => Heroku.user_agent
@@ -313,7 +330,7 @@ class Heroku::Auth
313
330
  :host => uri.host,
314
331
  :port => uri.port.to_s,
315
332
  :scheme => uri.scheme,
316
- :ssl_verify_peer => verify_ssl
333
+ :ssl_verify_peer => verify_host?(host)
317
334
  }
318
335
  end
319
336
  end
@@ -1,51 +1,81 @@
1
1
  require "heroku/client"
2
- require "digest/sha2"
3
2
 
4
3
  class Heroku::Client::HerokuPostgresql
5
- Version = 10
4
+ Version = 11
6
5
 
7
6
  include Heroku::Helpers
8
7
 
9
- def initialize(url)
8
+ @headers = { :x_heroku_gem_version => Heroku::Client.version }
9
+
10
+ def self.add_headers(headers)
11
+ @headers.merge! headers
12
+ end
13
+
14
+ def self.headers
15
+ @headers
16
+ end
17
+
18
+ attr_reader :attachment
19
+ def initialize(attachment)
20
+ @attachment = attachment
21
+ if attachment.resource_name == 'SHARED_DATABASE'
22
+ error('This command is not available for shared database')
23
+ end
10
24
  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 }
25
+ end
26
+
27
+ def heroku_postgresql_host
28
+ if attachment.starter_plan?
29
+ ENV["HEROKU_POSTGRESQL_HOST"] || "postgres-starter-api"
30
+ else
31
+ if ENV['SHOGUN']
32
+ "shogun-#{ENV['SHOGUN']}"
33
+ else
34
+ ENV["HEROKU_POSTGRESQL_HOST"] || "postgres-api"
35
+ end
36
+ end
37
+ end
38
+
39
+ def resource_name
40
+ attachment.resource_name
41
+ end
42
+
43
+ def heroku_postgresql_resource
44
+ RestClient::Resource.new(
45
+ "https://#{heroku_postgresql_host}.heroku.com/client/v11/databases",
46
+ :user => Heroku::Auth.user,
47
+ :password => Heroku::Auth.password,
48
+ :headers => self.class.headers
16
49
  )
17
50
  end
18
51
 
19
52
  def ingress
20
- http_put "#{@database_sha}/ingress"
53
+ http_put "#{resource_name}/ingress"
21
54
  end
22
55
 
23
56
  def reset
24
- http_put "#{@database_sha}/reset"
57
+ http_put "#{resource_name}/reset"
25
58
  end
26
59
 
27
60
  def rotate_credentials
28
- http_post "#{@database_sha}/credentials_rotation"
61
+ http_post "#{resource_name}/credentials_rotation"
29
62
  end
30
63
 
31
- def get_database
32
- http_get @database_sha
64
+ def get_database(extended=false)
65
+ query = extended ? '?extended=true' : ''
66
+ http_get resource_name + query
33
67
  end
34
68
 
35
69
  def get_wait_status
36
- http_get "#{@database_sha}/wait_status"
70
+ http_get "#{resource_name}/wait_status"
37
71
  end
38
72
 
39
73
  def unfollow
40
- http_put "#{@database_sha}/unfollow"
74
+ http_put "#{resource_name}/unfollow"
41
75
  end
42
76
 
43
77
  protected
44
78
 
45
- def sha(url)
46
- Digest::SHA2.hexdigest url
47
- end
48
-
49
79
  def sym_keys(c)
50
80
  if c.is_a?(Array)
51
81
  c.map { |e| sym_keys(e) }
@@ -77,7 +107,7 @@ class Heroku::Client::HerokuPostgresql
77
107
  def http_get(path)
78
108
  checking_client_version do
79
109
  retry_on_exception(RestClient::Exception) do
80
- response = @heroku_postgresql_resource[path].get
110
+ response = heroku_postgresql_resource[path].get
81
111
  display_heroku_warning response
82
112
  sym_keys(json_decode(response.to_s))
83
113
  end
@@ -86,7 +116,7 @@ class Heroku::Client::HerokuPostgresql
86
116
 
87
117
  def http_post(path, payload = {})
88
118
  checking_client_version do
89
- response = @heroku_postgresql_resource[path].post(json_encode(payload))
119
+ response = heroku_postgresql_resource[path].post(json_encode(payload))
90
120
  display_heroku_warning response
91
121
  sym_keys(json_decode(response.to_s))
92
122
  end
@@ -94,7 +124,7 @@ class Heroku::Client::HerokuPostgresql
94
124
 
95
125
  def http_put(path, payload = {})
96
126
  checking_client_version do
97
- response = @heroku_postgresql_resource[path].put(json_encode(payload))
127
+ response = heroku_postgresql_resource[path].put(json_encode(payload))
98
128
  display_heroku_warning response
99
129
  sym_keys(json_decode(response.to_s))
100
130
  end
@@ -1,6 +1,7 @@
1
1
  require "timeout"
2
2
  require "socket"
3
3
  require "uri"
4
+ require "heroku/auth"
4
5
  require "heroku/client"
5
6
  require "heroku/helpers"
6
7
 
@@ -29,10 +30,12 @@ class Heroku::Client::Rendezvous
29
30
 
30
31
  ssl_socket = Timeout.timeout(connect_timeout) do
31
32
  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__)
33
+
34
+ if Heroku::Auth.verify_host?(host)
35
+ ssl_context.ca_file = File.expand_path("../../../../data/cacert.pem", __FILE__)
34
36
  ssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER
35
37
  end
38
+
36
39
  tcp_socket = TCPSocket.open(host, port)
37
40
  ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, ssl_context)
38
41
  ssl_socket.connect
@@ -115,20 +115,12 @@ module Heroku
115
115
  if args.include?('-h') || args.include?('--help')
116
116
  args.unshift(cmd) unless cmd =~ /^-.*/
117
117
  cmd = 'help'
118
- command = parse('help')
118
+ command = parse(cmd)
119
119
  end
120
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
121
+ if cmd == '--version'
122
+ cmd = 'version'
123
+ command = parse(cmd)
132
124
  end
133
125
 
134
126
  @current_command = cmd
@@ -141,7 +133,7 @@ module Heroku
141
133
  # remove OptionParsers Officious['version'] to avoid conflicts
142
134
  # see: https://github.com/ruby/ruby/blob/trunk/lib/optparse.rb#L814
143
135
  parser.base.long.delete('version')
144
- (global_options + command[:options]).each do |option|
136
+ (global_options + (command && command[:options] || [])).each do |option|
145
137
  parser.on(*option[:args]) do |value|
146
138
  if option[:proc]
147
139
  option[:proc].call(value)
@@ -173,16 +165,39 @@ module Heroku
173
165
  @current_options = opts
174
166
  @invalid_arguments = invalid_options
175
167
 
176
- command_instance = command[:klass].new(args.dup, opts.dup)
177
-
178
168
  @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 _'
169
+ begin
170
+ usage_directory = "#{home_directory}/.heroku/usage"
171
+ FileUtils.mkdir_p(usage_directory)
172
+ usage_file = usage_directory << "/#{Heroku::VERSION}"
173
+ usage = if File.exists?(usage_file)
174
+ json_decode(File.read(usage_file))
175
+ else
176
+ {}
177
+ end
178
+ usage[@anonymous_command] ||= 0
179
+ usage[@anonymous_command] += 1
180
+ File.write(usage_file, json_encode(usage) + "\n")
181
+ rescue
182
+ # usage writing is not important, allow failures
182
183
  end
183
- @normalized_command = [ARGV.first, @normalized_args.sort_by {|arg| arg.gsub('-', '')}].join(' ')
184
184
 
185
- [ command_instance, command[:method] ]
185
+ if command
186
+ command_instance = command[:klass].new(args.dup, opts.dup)
187
+
188
+ if !@normalized_args.include?('--app _') && (implied_app = command_instance.app rescue nil)
189
+ @normalized_args << '--app _'
190
+ end
191
+ @normalized_command = [ARGV.first, @normalized_args.sort_by {|arg| arg.gsub('-', '')}].join(' ')
192
+
193
+ [ command_instance, command[:method] ]
194
+ else
195
+ error([
196
+ "`#{cmd}` is not a heroku command.",
197
+ suggestion(cmd, commands.keys + command_aliases.keys),
198
+ "See `heroku help` for a list of available commands."
199
+ ].compact.join("\n"))
200
+ end
186
201
  end
187
202
 
188
203
  def self.run(cmd, arguments=[])
@@ -233,6 +248,12 @@ module Heroku
233
248
  error e.message
234
249
  rescue OptionParser::ParseError
235
250
  commands[cmd] ? run("help", [cmd]) : run("help")
251
+ rescue Excon::Errors::SocketError => e
252
+ if e.message == 'getaddrinfo: nodename nor servname provided, or not known (SocketError)'
253
+ error("Unable to connect to Heroku API, please check internet connectivity and try again.")
254
+ else
255
+ raise(e)
256
+ end
236
257
  ensure
237
258
  display_warnings
238
259
  end
@@ -242,7 +263,7 @@ module Heroku
242
263
  end
243
264
 
244
265
  def self.extract_error(body, options={})
245
- default_error = block_given? ? yield : "Internal server error.\nRun 'heroku status' to check for known platform issues."
266
+ default_error = block_given? ? yield : "Internal server error.\nRun `heroku status` to check for known platform issues."
246
267
  parse_error_xml(body) || parse_error_json(body) || parse_error_plain(body) || default_error
247
268
  end
248
269
 
@@ -175,7 +175,7 @@ class Heroku::Command::Apps < Heroku::Command::Base
175
175
  # $ heroku apps:create myapp-staging --remote staging
176
176
  #
177
177
  def create
178
- name = shift_argument || options[:app]
178
+ name = shift_argument || options[:app] || ENV['HEROKU_APP']
179
179
  validate_arguments!
180
180
 
181
181
  info = api.post_app({ "name" => name, "stack" => options[:stack] }).body
@@ -206,7 +206,7 @@ class Heroku::Command::Apps < Heroku::Command::Base
206
206
 
207
207
  hputs([ info["web_url"], info["git_url"] ].join(" | "))
208
208
  rescue Timeout::Error
209
- hputs("Timed Out! Check heroku status for known issues.")
209
+ hputs("Timed Out! Run `heroku status` to check for known platform issues.")
210
210
  end
211
211
 
212
212
  unless options[:no_remote].is_a? FalseClass
@@ -4,29 +4,39 @@ require "heroku/command/base"
4
4
  #
5
5
  class Heroku::Command::Labs < Heroku::Command::Base
6
6
 
7
- # labs [APP]
7
+ # labs
8
8
  #
9
- # lists enabled features for an app
9
+ # list experimental features
10
10
  #
11
11
  #Example:
12
12
  #
13
- # $ heroku labs -a myapp
14
- # === myapp Enabled Features
15
- # sigterm-all: When stopping a dyno, send SIGTERM to all processes rather than only to the root process.
13
+ # === User Features (david@heroku.com)
14
+ # [+] dashboard Use Heroku Dashboard by default
16
15
  #
17
- # === email@example.com Enabled Features
18
- # sumo-rankings: Heroku Sumo ranks and visualizes the scale of your app, and suggests the optimum combination of dynos and add-ons to take it to the next level.
16
+ # === App Features (glacial-retreat-5913)
17
+ # [ ] preboot Provide seamless web dyno deploys
18
+ # [ ] user-env-compile Add user config vars to the environment during slug compilation # $ heroku labs -a myapp
19
19
  #
20
20
  def index
21
21
  validate_arguments!
22
22
 
23
- if app
24
- display_features(app, 'enabled', { 'enabled' => true, 'kind' => 'app' })
23
+ user_features, app_features = api.get_features(app).body.sort_by do |feature|
24
+ feature["name"]
25
+ end.partition do |feature|
26
+ feature["kind"] == "user"
25
27
  end
26
28
 
27
- display_features(Heroku::Auth.user, 'enabled', { 'enabled' => true, 'kind' => 'user' })
29
+ display_app = app || "no app specified"
30
+
31
+ styled_header "User Features (#{Heroku::Auth.user})"
32
+ display_features user_features
33
+ display
34
+ styled_header "App Features (#{display_app})"
35
+ display_features app_features
28
36
  end
29
37
 
38
+ alias_command "labs:list", "labs"
39
+
30
40
  # labs:info FEATURE
31
41
  #
32
42
  # displays additional information about FEATURE
@@ -54,107 +64,83 @@ class Heroku::Command::Labs < Heroku::Command::Base
54
64
 
55
65
  # labs:disable FEATURE
56
66
  #
57
- # disables FEATURE on an app
67
+ # disables an experimental feature
58
68
  #
59
69
  #Example:
60
70
  #
61
- # $ heroku labs:disable user_env_compile
62
- # Disabling user_env_compile for myapp... done
71
+ # $ heroku labs:disable ninja-power
72
+ # Disabling ninja-power feature for me@example.org... done
63
73
  #
64
74
  def disable
65
- unless feature_name = shift_argument
66
- error("Usage: heroku labs:disable FEATURE\nMust specify FEATURE to disable.")
67
- end
75
+ feature_name = shift_argument
76
+ error "Usage: heroku labs:disable FEATURE\nMust specify FEATURE to disable." unless feature_name
68
77
  validate_arguments!
69
78
 
70
- message = "Disabling #{feature_name}"
71
- message += " for #{app}" if app
72
- action(message) do
73
- api.delete_feature(feature_name, app)
79
+ feature = api.get_features(app).body.detect { |f| f["name"] == feature_name }
80
+ message = "Disabling #{feature_name} "
81
+
82
+ error "No such feature: #{feature_name}" unless feature
83
+
84
+ if feature["kind"] == "user"
85
+ message += "for #{Heroku::Auth.user}"
86
+ else
87
+ error "Must specify an app" unless app
88
+ message += "for #{app}"
89
+ end
90
+
91
+ action message do
92
+ api.delete_feature feature_name, app
74
93
  end
75
94
  end
76
95
 
77
96
  # labs:enable FEATURE
78
97
  #
79
- # enables FEATURE on an app
98
+ # enables an experimental feature
80
99
  #
81
100
  #Example:
82
101
  #
83
- # $ heroku labs:enable user_env_compile
84
- # Enabling user_env_compile for myapp... done
85
- # For more information see: http://devcenter.heroku.com/articles/labs-user-env-compile
102
+ # $ heroku labs:enable ninja-power
103
+ # Enabling ninja-power feature for me@example.org... done
86
104
  #
87
105
  def enable
88
- unless feature_name = shift_argument
89
- error("Usage: heroku labs:enable FEATURE\nMust specify FEATURE to enable.")
90
- end
106
+ feature_name = shift_argument
107
+ error "Usage: heroku labs:enable FEATURE\nMust specify FEATURE to enable." unless feature_name
91
108
  validate_arguments!
92
109
 
93
- message = "Enabling #{feature_name}"
94
- message += " for #{app}" if app
95
- feature_data = nil
96
- action(message) do
97
- feature_data = api.post_feature(feature_name, app).body
110
+ feature = api.get_features.body.detect { |f| f["name"] == feature_name }
111
+ message = "Enabling #{feature_name} "
112
+
113
+ error "No such feature: #{feature_name}" unless feature
114
+
115
+ if feature["kind"] == "user"
116
+ message += "for #{Heroku::Auth.user}"
117
+ else
118
+ error "Must specify an app" unless app
119
+ message += "for #{app}"
98
120
  end
99
- display("WARNING: This feature is experimental and may change or be removed without notice.")
100
- display("For more information see: #{feature_data['docs']}")
101
- end
102
121
 
103
- # labs:list
104
- #
105
- # lists available features
106
- #
107
- #Example:
108
- #
109
- # $ heroku labs:list
110
- # === App Available Features
111
- # dot-profile: Source .profile during dyno startup
112
- # preboot: Provide seamless deploys by booting web dynos with new code before killing existing web dynos.
113
- # user_env_compile: Add user config vars to the environment during slug compilation
114
- #
115
- # === User Available Features
116
- # default-heroku-postgresql-dev: Use the new heroku-postgresql:dev add-on as the default database for Cedar apps.
117
- #
118
- def list
119
- validate_arguments!
122
+ feature_data = action(message) do
123
+ api.post_feature(feature_name, app).body
124
+ end
120
125
 
121
- display_features('App', 'available', { 'kind' => 'app' })
122
- display_features('User', 'available', { 'kind' => 'user' })
126
+ display "WARNING: This feature is experimental and may change or be removed without notice."
127
+ display "For more information see: #{feature_data["docs"]}" if feature_data["docs"]
123
128
  end
124
129
 
125
130
  private
126
131
 
132
+ # app is not required for these commands, so rescue if there is none
127
133
  def app
128
- # app is not required for these commands, so rescue if there is none
129
134
  super
130
135
  rescue Heroku::Command::CommandFailed
131
136
  nil
132
137
  end
133
138
 
134
- def display_features(type, status, attributes)
135
- @features ||= api.get_features(app).body
136
-
137
- selected_features = @features.dup
138
- attributes.each do |key, value|
139
- selected_features = selected_features.select {|feature| feature[key] == value}
140
- end
141
-
142
- feature_hash = {}
143
- selected_features.each do |feature|
144
- feature_hash[feature['name']] = feature['summary']
145
- end
146
-
147
- if feature_hash.empty?
148
- case status
149
- when 'available'
150
- display("There are no #{type} features available.")
151
- when 'enabled'
152
- display("#{type} has no enabled features.")
153
- end
154
- else
155
- styled_header("#{type} #{status.capitalize} Features")
156
- styled_hash(feature_hash)
157
- display
139
+ def display_features(features)
140
+ longest_name = features.map { |f| f["name"].to_s.length }.sort.last
141
+ features.each do |feature|
142
+ toggle = feature["enabled"] ? "[+]" : "[ ]"
143
+ display "%s %-#{longest_name}s %s" % [ toggle, feature["name"], feature["summary"] ]
158
144
  end
159
145
  end
160
146