artofmission-heroku 1.6.3

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 (47) hide show
  1. data/README.md +66 -0
  2. data/Rakefile +107 -0
  3. data/bin/heroku +15 -0
  4. data/lib/heroku.rb +5 -0
  5. data/lib/heroku/client.rb +487 -0
  6. data/lib/heroku/command.rb +96 -0
  7. data/lib/heroku/commands/account.rb +13 -0
  8. data/lib/heroku/commands/addons.rb +109 -0
  9. data/lib/heroku/commands/app.rb +239 -0
  10. data/lib/heroku/commands/auth.rb +137 -0
  11. data/lib/heroku/commands/base.rb +133 -0
  12. data/lib/heroku/commands/bundles.rb +51 -0
  13. data/lib/heroku/commands/config.rb +55 -0
  14. data/lib/heroku/commands/db.rb +129 -0
  15. data/lib/heroku/commands/domains.rb +31 -0
  16. data/lib/heroku/commands/help.rb +148 -0
  17. data/lib/heroku/commands/keys.rb +49 -0
  18. data/lib/heroku/commands/logs.rb +11 -0
  19. data/lib/heroku/commands/maintenance.rb +13 -0
  20. data/lib/heroku/commands/plugins.rb +25 -0
  21. data/lib/heroku/commands/ps.rb +37 -0
  22. data/lib/heroku/commands/service.rb +23 -0
  23. data/lib/heroku/commands/sharing.rb +29 -0
  24. data/lib/heroku/commands/ssl.rb +33 -0
  25. data/lib/heroku/commands/version.rb +7 -0
  26. data/lib/heroku/helpers.rb +23 -0
  27. data/lib/heroku/plugin.rb +65 -0
  28. data/spec/base.rb +23 -0
  29. data/spec/client_spec.rb +366 -0
  30. data/spec/command_spec.rb +15 -0
  31. data/spec/commands/addons_spec.rb +47 -0
  32. data/spec/commands/app_spec.rb +175 -0
  33. data/spec/commands/auth_spec.rb +104 -0
  34. data/spec/commands/base_spec.rb +114 -0
  35. data/spec/commands/bundles_spec.rb +48 -0
  36. data/spec/commands/config_spec.rb +45 -0
  37. data/spec/commands/db_spec.rb +53 -0
  38. data/spec/commands/domains_spec.rb +31 -0
  39. data/spec/commands/keys_spec.rb +60 -0
  40. data/spec/commands/logs_spec.rb +21 -0
  41. data/spec/commands/maintenance_spec.rb +21 -0
  42. data/spec/commands/plugins_spec.rb +26 -0
  43. data/spec/commands/ps_spec.rb +16 -0
  44. data/spec/commands/sharing_spec.rb +32 -0
  45. data/spec/commands/ssl_spec.rb +25 -0
  46. data/spec/plugin_spec.rb +64 -0
  47. metadata +150 -0
@@ -0,0 +1,66 @@
1
+ Heroku API - deploy apps to Heroku from the command line
2
+ ========================================================
3
+
4
+ This library wraps the REST API for managing and deploying Rails apps to the
5
+ Heroku platform. It can be called as a Ruby library, or invoked from the
6
+ command line. Code push and pull is done through Git.
7
+
8
+ For more about Heroku see <http://heroku.com>.
9
+
10
+ For full documentation see <http://heroku.com/docs>.
11
+
12
+
13
+ Sample Workflow
14
+ ---------------
15
+
16
+ Create a new Rails app and deploy it:
17
+
18
+ rails myapp && cd myapp # Create an app
19
+ git init # Init git repository
20
+ git add . # Add everything
21
+ git commit -m Initial # Commit everything
22
+ heroku create # Create your app on Heroku
23
+ git push heroku master # Deploy your app on Heroku
24
+
25
+
26
+ Setup
27
+ -----
28
+
29
+ gem install heroku
30
+
31
+ If you wish to push or pull code, you must also have a working install of Git
32
+ ("apt-get install git-core" on Ubuntu or "port install git-core" on OS X), and
33
+ an ssh public key ("ssh-keygen -t rsa").
34
+
35
+ The first time you run a command, such as "heroku list," you will be prompted
36
+ for your Heroku username and password. If you're on a Mac, these are saved to
37
+ your Keychain. Otherwise they are stored in plain text in ~/heroku/credentials
38
+ for future requests.
39
+
40
+ Your public key (~/.ssh/id_[rd]sa.pub) will be uploaded to Heroku after you
41
+ enter your credentials. Use heroku keys:add if you wish to upload additional
42
+ keys or specify a key in a non-standard location.
43
+
44
+ Meta
45
+ ----
46
+
47
+ Created by Adam Wiggins
48
+
49
+ Maintained by Pedro Belo
50
+
51
+ Patches contributed by:
52
+
53
+ * Chris O'Sullivan
54
+ * Blake Mizerany
55
+ * Ricardo Chimal
56
+ * Les Hill
57
+ * Ryan Tomayko
58
+ * Sarah Mei
59
+ * Nick Quaranto
60
+ * Matt Buck
61
+ * Terence Lee
62
+ * Caio Chassot
63
+
64
+
65
+ Released under the [MIT license](http://www.opensource.org/licenses/mit-license.php).
66
+ <http://github.com/heroku/heroku>
@@ -0,0 +1,107 @@
1
+ require 'rake'
2
+ require 'spec/rake/spectask'
3
+
4
+ desc "Run all specs"
5
+ Spec::Rake::SpecTask.new('spec') do |t|
6
+ t.spec_opts = ['--colour --format progress --loadby mtime --reverse']
7
+ t.spec_files = FileList['spec/**/*_spec.rb']
8
+ end
9
+
10
+ desc "Print specdocs"
11
+ Spec::Rake::SpecTask.new(:doc) do |t|
12
+ t.spec_opts = ["--format", "specdoc", "--dry-run"]
13
+ t.spec_files = FileList['spec/*_spec.rb']
14
+ end
15
+
16
+ desc "Generate RCov code coverage report"
17
+ Spec::Rake::SpecTask.new('rcov') do |t|
18
+ t.spec_files = FileList['spec/*_spec.rb']
19
+ t.rcov = true
20
+ t.rcov_opts = ['--exclude', 'examples']
21
+ end
22
+
23
+ task :default => :spec
24
+
25
+ ######################################################
26
+
27
+ require 'rake'
28
+ require 'rake/testtask'
29
+ require 'rake/clean'
30
+ require 'rake/gempackagetask'
31
+ require 'rake/rdoctask'
32
+ require 'fileutils'
33
+ include FileUtils
34
+
35
+ begin
36
+ require 'lib/heroku'
37
+ version = Heroku::Client.version
38
+ rescue LoadError
39
+ version = ""
40
+
41
+ puts "ERROR: Missing one or more dependencies. Make sure jeweler is installed and run: rake check_dependencies"
42
+ puts
43
+ end
44
+
45
+ name = "heroku"
46
+
47
+ spec = Gem::Specification.new do |s|
48
+ s.name = name
49
+ s.version = version
50
+ s.summary = "Client library and CLI to deploy Rails apps on Heroku."
51
+ s.description = "Client library and command-line tool to manage and deploy Rails apps on Heroku."
52
+ s.author = "Heroku"
53
+ s.email = "support@heroku.com"
54
+ s.homepage = "http://heroku.com/"
55
+ s.executables = [ "heroku" ]
56
+ s.default_executable = "heroku"
57
+ s.rubyforge_project = "heroku"
58
+
59
+ s.platform = Gem::Platform::RUBY
60
+ s.has_rdoc = false
61
+
62
+ s.files = %w(Rakefile) +
63
+ Dir.glob("{bin,lib,spec}/**/*")
64
+
65
+ s.require_path = "lib"
66
+ s.bindir = "bin"
67
+
68
+ s.add_development_dependency 'rake'
69
+ s.add_development_dependency 'rspec', '~> 1.2.0'
70
+ s.add_development_dependency 'taps', '~> 0.2.23'
71
+
72
+ s.add_dependency 'rest-client', '~> 1.2'
73
+ s.add_dependency 'launchy', '~> 0.3.2'
74
+ s.add_dependency 'json', '~> 1.2.0'
75
+ end
76
+
77
+ Rake::GemPackageTask.new(spec) do |p|
78
+ p.need_tar = true if RUBY_PLATFORM !~ /mswin/
79
+ end
80
+
81
+ desc "Install #{name} gem (#{version})"
82
+ task :install => [ :test, :package ] do
83
+ sh %{sudo gem install pkg/#{name}-#{version}.gem}
84
+ end
85
+
86
+ desc "Uninstall #{name} gem"
87
+ task :uninstall => [ :clean ] do
88
+ sh %{sudo gem uninstall #{name}}
89
+ end
90
+
91
+ Rake::TestTask.new do |t|
92
+ t.libs << "spec"
93
+ t.test_files = FileList['spec/*_spec.rb']
94
+ t.verbose = true
95
+ end
96
+
97
+ CLEAN.include [ 'build/*', '**/*.o', '**/*.so', '**/*.a', 'lib/*-*', '**/*.log', 'pkg', 'lib/*.bundle', '*.gem', '.config' ]
98
+
99
+ begin
100
+ require 'jeweler'
101
+ Jeweler::Tasks.new(spec) do |s|
102
+ s.version = version
103
+ end
104
+ Jeweler::RubyforgeTasks.new
105
+ rescue LoadError
106
+ puts "Jeweler not available. Install it with: sudo gem install jeweler"
107
+ end
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+
5
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../lib')
6
+
7
+ require 'heroku'
8
+ require 'heroku/command'
9
+
10
+ args = ARGV.dup
11
+ ARGV.clear
12
+ command = args.shift.strip rescue 'help'
13
+
14
+ Heroku::Command.run(command, args)
15
+
@@ -0,0 +1,5 @@
1
+ module Heroku; end
2
+
3
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/heroku')
4
+
5
+ require 'client'
@@ -0,0 +1,487 @@
1
+ require 'rexml/document'
2
+ require 'rest_client'
3
+ require 'uri'
4
+ require 'time'
5
+ require 'json'
6
+
7
+ # A Ruby class to call the Heroku REST API. You might use this if you want to
8
+ # manage your Heroku apps from within a Ruby program, such as Capistrano.
9
+ #
10
+ # Example:
11
+ #
12
+ # require 'heroku'
13
+ # heroku = Heroku::Client.new('me@example.com', 'mypass')
14
+ # heroku.create('myapp')
15
+ #
16
+ class Heroku::Client
17
+ def self.version
18
+ '1.6.3'
19
+ end
20
+
21
+ def self.gem_version_string
22
+ "heroku-gem/#{version}"
23
+ end
24
+
25
+ attr_reader :host, :user, :password
26
+
27
+ def initialize(user, password, host='heroku.com')
28
+ @user = user
29
+ @password = password
30
+ @host = host
31
+ end
32
+
33
+ # Show a list of apps which you are a collaborator on.
34
+ def list
35
+ doc = xml(get('/apps'))
36
+ doc.elements.to_a("//apps/app").map do |a|
37
+ name = a.elements.to_a("name").first
38
+ owner = a.elements.to_a("owner").first
39
+ [name.text, owner.text]
40
+ end
41
+ end
42
+
43
+ # Show info such as mode, custom domain, and collaborators on an app.
44
+ def info(name_or_domain)
45
+ name_or_domain = name_or_domain.gsub(/^(http:\/\/)?(www\.)?/, '')
46
+ doc = xml(get("/apps/#{name_or_domain}"))
47
+ attrs = doc.elements.to_a('//app/*').inject({}) do |hash, element|
48
+ hash[element.name.gsub(/-/, '_').to_sym] = element.text; hash
49
+ end
50
+ attrs.merge!(:collaborators => list_collaborators(attrs[:name]))
51
+ attrs.merge!(:addons => installed_addons(attrs[:name]))
52
+ end
53
+
54
+ # Create a new app, with an optional name.
55
+ def create(name=nil, options={})
56
+ options[:name] = name if name
57
+ xml(post('/apps', :app => options)).elements["//app/name"].text
58
+ end
59
+
60
+ # Update an app. Available attributes:
61
+ # :name => rename the app (changes http and git urls)
62
+ def update(name, attributes)
63
+ put("/apps/#{name}", :app => attributes)
64
+ end
65
+
66
+ # Destroy the app permanently.
67
+ def destroy(name)
68
+ delete("/apps/#{name}")
69
+ end
70
+
71
+ # Get a list of collaborators on the app, returns an array of hashes each with :email
72
+ def list_collaborators(app_name)
73
+ doc = xml(get("/apps/#{app_name}/collaborators"))
74
+ doc.elements.to_a("//collaborators/collaborator").map do |a|
75
+ { :email => a.elements['email'].text }
76
+ end
77
+ end
78
+
79
+ # Invite a person by email address to collaborate on the app.
80
+ def add_collaborator(app_name, email)
81
+ xml(post("/apps/#{app_name}/collaborators", { 'collaborator[email]' => email }))
82
+ rescue RestClient::RequestFailed => e
83
+ raise e unless e.http_code == 422
84
+ e.response.body
85
+ end
86
+
87
+ # Remove a collaborator.
88
+ def remove_collaborator(app_name, email)
89
+ delete("/apps/#{app_name}/collaborators/#{escape(email)}")
90
+ end
91
+
92
+ def list_domains(app_name)
93
+ doc = xml(get("/apps/#{app_name}/domains"))
94
+ doc.elements.to_a("//domain-names/*").map do |d|
95
+ attrs = { :domain => d.elements['domain'].text }
96
+ if cert = d.elements['cert']
97
+ attrs[:cert] = {
98
+ :expires_at => Time.parse(cert.elements['expires-at'].text),
99
+ :subject => cert.elements['subject'].text,
100
+ :issuer => cert.elements['issuer'].text,
101
+ }
102
+ end
103
+ attrs
104
+ end
105
+ end
106
+
107
+ def add_domain(app_name, domain)
108
+ post("/apps/#{app_name}/domains", domain)
109
+ end
110
+
111
+ def remove_domain(app_name, domain)
112
+ delete("/apps/#{app_name}/domains/#{domain}")
113
+ end
114
+
115
+ def remove_domains(app_name)
116
+ delete("/apps/#{app_name}/domains")
117
+ end
118
+
119
+ def add_ssl(app_name, pem, key)
120
+ JSON.parse(post("/apps/#{app_name}/ssl", :pem => pem, :key => key))
121
+ end
122
+
123
+ def remove_ssl(app_name, domain)
124
+ delete("/apps/#{app_name}/domains/#{domain}/ssl")
125
+ end
126
+
127
+ # Get the list of ssh public keys for the current user.
128
+ def keys
129
+ doc = xml get('/user/keys')
130
+ doc.elements.to_a('//keys/key').map do |key|
131
+ key.elements['contents'].text
132
+ end
133
+ end
134
+
135
+ # Add an ssh public key to the current user.
136
+ def add_key(key)
137
+ post("/user/keys", key, { 'Content-Type' => 'text/ssh-authkey' })
138
+ end
139
+
140
+ # Remove an existing ssh public key from the current user.
141
+ def remove_key(key)
142
+ delete("/user/keys/#{escape(key)}")
143
+ end
144
+
145
+ # Clear all keys on the current user.
146
+ def remove_all_keys
147
+ delete("/user/keys")
148
+ end
149
+
150
+ class AppCrashed < RuntimeError; end
151
+
152
+ # Run a rake command on the Heroku app and return all output as
153
+ # a string.
154
+ def rake(app_name, cmd)
155
+ start(app_name, "rake #{cmd}", attached=true).to_s
156
+ end
157
+
158
+ # support for console sessions
159
+ class ConsoleSession
160
+ def initialize(id, app, client)
161
+ @id = id; @app = app; @client = client
162
+ end
163
+ def run(cmd)
164
+ @client.run_console_command("/apps/#{@app}/consoles/#{@id}/command", cmd, "=> ")
165
+ end
166
+ end
167
+
168
+ # Execute a one-off console command, or start a new console tty session if
169
+ # cmd is nil.
170
+ def console(app_name, cmd=nil)
171
+ if block_given?
172
+ id = post("/apps/#{app_name}/consoles")
173
+ yield ConsoleSession.new(id, app_name, self)
174
+ delete("/apps/#{app_name}/consoles/#{id}")
175
+ else
176
+ run_console_command("/apps/#{app_name}/console", cmd)
177
+ end
178
+ rescue RestClient::RequestFailed => e
179
+ raise(AppCrashed, e.response.body) if e.response.code.to_i == 502
180
+ raise e
181
+ end
182
+
183
+ # internal method to run console commands formatting the output
184
+ def run_console_command(url, command, prefix=nil)
185
+ output = post(url, command)
186
+ return output unless prefix
187
+ if output.include?("\n")
188
+ lines = output.split("\n")
189
+ (lines[0..-2] << "#{prefix}#{lines.last}").join("\n")
190
+ else
191
+ prefix + output
192
+ end
193
+ rescue RestClient::RequestFailed => e
194
+ raise e unless e.http_code == 422
195
+ e.http_body
196
+ end
197
+
198
+ class Service
199
+ attr_accessor :attached, :upid
200
+
201
+ def initialize(client, app, upid=nil)
202
+ @client = client
203
+ @app = app
204
+ @upid = upid
205
+ end
206
+
207
+ # start the service
208
+ def start(command, attached=false)
209
+ @attached = attached
210
+ @response = @client.post(
211
+ "/apps/#{@app}/services",
212
+ command,
213
+ :content_type => 'text/plain'
214
+ )
215
+ @next_chunk = @response
216
+ @interval = 0
217
+ self
218
+ rescue RestClient::RequestFailed => e
219
+ raise AppCrashed, e.http_body if e.http_code == 502
220
+ raise
221
+ end
222
+
223
+ def transition(action)
224
+ @response = @client.put(
225
+ "/apps/#{@app}/services/#{@upid}",
226
+ action,
227
+ :content_type => 'text/plain'
228
+ )
229
+ self
230
+ rescue RestClient::RequestFailed => e
231
+ raise AppCrashed, e.http_body if e.http_code == 502
232
+ raise
233
+ end
234
+
235
+ def down ; transition('down') ; end
236
+ def up ; transition('up') ; end
237
+ def bounce ; transition('bounce') ; end
238
+
239
+ # Does the service have any remaining output?
240
+ def end_of_stream?
241
+ @next_chunk.nil?
242
+ end
243
+
244
+ # Read the next chunk of output.
245
+ def read
246
+ chunk = @client.get(@next_chunk)
247
+ if chunk.nil? or chunk == ''
248
+ # assume no content and back off
249
+ @interval = 2
250
+ ''
251
+ elsif location = chunk.headers[:location]
252
+ # some data read and next chunk available
253
+ @next_chunk = location
254
+ @interval = 0
255
+ chunk
256
+ else
257
+ # no more chunks
258
+ @next_chunk = nil
259
+ chunk
260
+ end
261
+ end
262
+
263
+ # Iterate over all output chunks until EOF is reached.
264
+ def each
265
+ until end_of_stream?
266
+ sleep(@interval)
267
+ output = read
268
+ yield output unless output.empty?
269
+ end
270
+ end
271
+
272
+ # All output as a string
273
+ def to_s
274
+ buf = []
275
+ each { |part| buf << part }
276
+ buf.join
277
+ end
278
+ end
279
+
280
+ # Retreive ps list for the given app name.
281
+ def ps(app_name)
282
+ JSON.parse resource("/apps/#{app_name}/ps").get(:accept => 'application/json')
283
+ end
284
+
285
+ # Run a service. If Responds to #each and yields output as it's received.
286
+ def start(app_name, command, attached=false)
287
+ service = Service.new(self, app_name)
288
+ service.start(command, attached)
289
+ end
290
+
291
+ # Get a Service instance to execute commands against.
292
+ def service(app_name, upid)
293
+ Service.new(self, app_name, upid)
294
+ end
295
+
296
+ # Bring a service up.
297
+ def up(app_name, upid)
298
+ service(app_name, upid).up
299
+ end
300
+
301
+ # Bring a service down.
302
+ def down(app_name, upid)
303
+ service(app_name, upid).down
304
+ end
305
+
306
+ # Bounce a service.
307
+ def bounce(app_name, upid)
308
+ service(app_name, upid).bounce
309
+ end
310
+
311
+
312
+ # Restart the app servers.
313
+ def restart(app_name)
314
+ delete("/apps/#{app_name}/server")
315
+ end
316
+
317
+ # Fetch recent logs from the app server.
318
+ def logs(app_name)
319
+ get("/apps/#{app_name}/logs")
320
+ end
321
+
322
+ # Fetch recent cron logs from the app server.
323
+ def cron_logs(app_name)
324
+ get("/apps/#{app_name}/cron_logs")
325
+ end
326
+
327
+ # Scales the web processes.
328
+ def set_dynos(app_name, qty)
329
+ put("/apps/#{app_name}/dynos", :dynos => qty).to_i
330
+ end
331
+
332
+ # Scales the background processes.
333
+ def set_workers(app_name, qty)
334
+ put("/apps/#{app_name}/workers", :workers => qty).to_i
335
+ end
336
+
337
+ # Capture a bundle from the given app, as a backup or for download.
338
+ def bundle_capture(app_name, bundle_name=nil)
339
+ xml(post("/apps/#{app_name}/bundles", :bundle => { :name => bundle_name })).elements["//bundle/name"].text
340
+ end
341
+
342
+ def bundle_destroy(app_name, bundle_name)
343
+ delete("/apps/#{app_name}/bundles/#{bundle_name}")
344
+ end
345
+
346
+ # Get a temporary URL where the bundle can be downloaded.
347
+ # If bundle_name is nil it will use the most recently captured bundle for the app
348
+ def bundle_url(app_name, bundle_name=nil)
349
+ bundle = JSON.parse(get("/apps/#{app_name}/bundles/#{bundle_name || 'latest'}", { :accept => 'application/json' }))
350
+ bundle['temporary_url']
351
+ end
352
+
353
+ def bundle_download(app_name, fname, bundle_name=nil)
354
+ warn "[DEPRECATION] `bundle_download` is deprecated. Please use `bundle_url` instead"
355
+ data = RestClient.get(bundle_url(app_name, bundle_name))
356
+ File.open(fname, "wb") { |f| f.write data }
357
+ end
358
+
359
+ # Get a list of bundles of the app.
360
+ def bundles(app_name)
361
+ doc = xml(get("/apps/#{app_name}/bundles"))
362
+ doc.elements.to_a("//bundles/bundle").map do |a|
363
+ {
364
+ :name => a.elements['name'].text,
365
+ :state => a.elements['state'].text,
366
+ :created_at => Time.parse(a.elements['created-at'].text),
367
+ }
368
+ end
369
+ end
370
+
371
+ def config_vars(app_name)
372
+ JSON.parse get("/apps/#{app_name}/config_vars")
373
+ end
374
+
375
+ def add_config_vars(app_name, new_vars)
376
+ put("/apps/#{app_name}/config_vars", new_vars.to_json)
377
+ end
378
+
379
+ def remove_config_var(app_name, key)
380
+ delete("/apps/#{app_name}/config_vars/#{key}")
381
+ end
382
+
383
+ def clear_config_vars(app_name)
384
+ delete("/apps/#{app_name}/config_vars")
385
+ end
386
+
387
+ def addons
388
+ JSON.parse get("/addons", :accept => 'application/json')
389
+ end
390
+
391
+ def installed_addons(app_name)
392
+ JSON.parse get("/apps/#{app_name}/addons", :accept => 'application/json')
393
+ end
394
+
395
+ def install_addon(app_name, addon, config={})
396
+ post("/apps/#{app_name}/addons/#{escape(addon)}", { :config => config }, :accept => 'application/json')
397
+ end
398
+
399
+ def uninstall_addon(app_name, addon)
400
+ delete("/apps/#{app_name}/addons/#{escape(addon)}", :accept => 'application/json')
401
+ end
402
+
403
+ def confirm_billing
404
+ post("/user/#{escape(@user)}/confirm_billing")
405
+ end
406
+
407
+ def on_warning(&blk)
408
+ @warning_callback = blk
409
+ end
410
+
411
+ ##################
412
+
413
+ def resource(uri)
414
+ RestClient.proxy = ENV['HTTP_PROXY']
415
+ if uri =~ /^https?/
416
+ RestClient::Resource.new(uri, user, password)
417
+ else
418
+ RestClient::Resource.new("https://api.#{host}", user, password)[uri]
419
+ end
420
+ end
421
+
422
+ def get(uri, extra_headers={}) # :nodoc:
423
+ process(:get, uri, extra_headers)
424
+ end
425
+
426
+ def post(uri, payload="", extra_headers={}) # :nodoc:
427
+ process(:post, uri, extra_headers, payload)
428
+ end
429
+
430
+ def put(uri, payload, extra_headers={}) # :nodoc:
431
+ process(:put, uri, extra_headers, payload)
432
+ end
433
+
434
+ def delete(uri, extra_headers={}) # :nodoc:
435
+ process(:delete, uri, extra_headers)
436
+ end
437
+
438
+ def process(method, uri, extra_headers={}, payload=nil)
439
+ headers = heroku_headers.merge(extra_headers)
440
+ args = [method, payload, headers].compact
441
+ response = resource(uri).send(*args)
442
+
443
+ extract_warning(response)
444
+ response
445
+ end
446
+
447
+ def extract_warning(response)
448
+ return unless response
449
+ if response.headers[:x_heroku_warning] && @warning_callback
450
+ warning = response.headers[:x_heroku_warning]
451
+ @displayed_warnings ||= {}
452
+ unless @displayed_warnings[warning]
453
+ @warning_callback.call(warning)
454
+ @displayed_warnings[warning] = true
455
+ end
456
+ end
457
+ end
458
+
459
+ def heroku_headers # :nodoc:
460
+ {
461
+ 'X-Heroku-API-Version' => '2',
462
+ 'User-Agent' => self.class.gem_version_string,
463
+ }
464
+ end
465
+
466
+ def xml(raw) # :nodoc:
467
+ REXML::Document.new(raw)
468
+ end
469
+
470
+ def escape(value) # :nodoc:
471
+ escaped = URI.escape(value.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
472
+ escaped.gsub('.', '%2E') # not covered by the previous URI.escape
473
+ end
474
+
475
+ def database_session(app_name)
476
+ post("/apps/#{app_name}/database/session", '')
477
+ end
478
+
479
+ def database_reset(app_name)
480
+ post("/apps/#{app_name}/database/reset", '')
481
+ end
482
+
483
+ def maintenance(app_name, mode)
484
+ mode = mode == :on ? '1' : '0'
485
+ post("/apps/#{app_name}/server/maintenance", :maintenance_mode => mode)
486
+ end
487
+ end