heroku-platform-api 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +141 -0
- data/Rakefile +1 -0
- data/heroku.gemspec +26 -0
- data/lib/heroku/api.rb +19 -0
- data/lib/heroku/api/account.rb +38 -0
- data/lib/heroku/api/app.rb +61 -0
- data/lib/heroku/api/apps.rb +31 -0
- data/lib/heroku/api/password.rb +18 -0
- data/lib/heroku/api/rate_limits.rb +24 -0
- data/lib/heroku/api/regions.rb +13 -0
- data/lib/heroku/conn.rb +91 -0
- data/lib/heroku/conn/cache.rb +47 -0
- data/lib/heroku/model.rb +9 -0
- data/lib/heroku/model/account.rb +45 -0
- data/lib/heroku/model/app.rb +70 -0
- data/lib/heroku/model/app_list.rb +21 -0
- data/lib/heroku/model/array_proxy.rb +27 -0
- data/lib/heroku/model/model_helper.rb +17 -0
- data/lib/heroku/properties.rb +36 -0
- data/lib/heroku/properties/null_logger.rb +16 -0
- data/lib/heroku/version.rb +3 -0
- data/lib/heroku_api.rb +6 -0
- data/spec/heroku/conn/cache_spec.rb +29 -0
- data/spec/heroku/conn_spec.rb +14 -0
- data/spec/heroku/model/model_helper_spec.rb +70 -0
- data/spec/heroku/properties/null_logger_spec.rb +25 -0
- data/spec/heroku/properties_spec.rb +45 -0
- data/spec/spec_helper.rb +15 -0
- metadata +141 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
ZTQ3NDk4YzM0YjRjNGYwZmM5NmZkYTkzMjBmYTI2NjU5M2FkYTY2ZQ==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
NGVhZmFmYjZlOWExNDNiNDRhOGY5Y2I5ZDI2N2I5NWRmMjdhMTNiNA==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
OTY5YWYxOWQ5Y2RiYmMwOWEzMDZkM2FjYmZjY2JhMWY1N2NhNjA1NTg5NmRl
|
10
|
+
OWIxNDJmMTY0ZDIxYWJkOGNmZjRmNzBlMmFhOWQ5MWUxODc0NjBlNmQ0NjQw
|
11
|
+
OWZhMmM4YWZiY2RmMDg4NWQ3NGU3OTRjZjc0YWNkYTAwY2U1OTA=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
YTUyYjg1NTY5NWM2YTM2M2YzYzAyNGNkMDY1OGI5YzZjMjc1NTM5YWI4MGE5
|
14
|
+
YWUzY2RkNDc2MjdkMjU4YzE1OWMwZWQ3NTE4NjI3NDhhYzYxNGQwMjBlNGUz
|
15
|
+
YTJhNjNlZjY0NDljYzQ0OTQ5NDE0M2U5ZGY3MDYzNzU0ZThhY2Y=
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Ashok Menon
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
# Heroku
|
2
|
+
|
3
|
+
Create, destroy and manage your heroku applications programmatically, using the Heroku Platform API.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'heroku-platform-api'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install heroku-platform-api
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
### Require the gem
|
22
|
+
|
23
|
+
require 'heroku_api'
|
24
|
+
# => true
|
25
|
+
|
26
|
+
### Configuration
|
27
|
+
|
28
|
+
Heroku::API.configure do |c|
|
29
|
+
c.api_key = "<Heroku API token>" # Mandatory
|
30
|
+
c.logger = Logger.new($stdout) # Optional
|
31
|
+
end
|
32
|
+
|
33
|
+
### Apps
|
34
|
+
|
35
|
+
The `Heroku::API.apps` object can be treated as though it were an `Array` of applications
|
36
|
+
with the below methods added.
|
37
|
+
|
38
|
+
#### Creating a new App
|
39
|
+
|
40
|
+
app = Heroku::API.apps.new(name: "example", region: {name: 'us'}, stack: 'cedar')
|
41
|
+
# => #<Heroku::Model::App id="01234567-89ab-cdef-0123-456789abcdef", name="example">
|
42
|
+
|
43
|
+
All the parameters provided are optional, if this end-point is called without them,
|
44
|
+
then defaults will be chosen.
|
45
|
+
|
46
|
+
#### Listing all Available Apps
|
47
|
+
|
48
|
+
Heroku::API.apps.all
|
49
|
+
# => [#<Heroku::Model App...>,...]
|
50
|
+
|
51
|
+
#### Searching for a Particular App
|
52
|
+
|
53
|
+
Heroku::API.apps["example"]
|
54
|
+
# => #<Heroku::Model::App id="01234567-89ab-cdef-0123-456789abcdef", name="example">
|
55
|
+
|
56
|
+
OR
|
57
|
+
|
58
|
+
Heroku::API.apps.app("example")
|
59
|
+
# => #<Heroku::Model::App id="01234567-89ab-cdef-0123-456789abcdef", name="example">
|
60
|
+
|
61
|
+
#### Updating an Existing App
|
62
|
+
|
63
|
+
app.name = "my_app" # Name and heroku based sub-domain of app.
|
64
|
+
app.maintenance = true # Maintenance mode on.
|
65
|
+
app.save
|
66
|
+
# => #<Heroku::Model::App id="01234567-89ab-cdef-0123-456789abcdef", name="my_app">
|
67
|
+
|
68
|
+
#### Pushing to an Existing App
|
69
|
+
|
70
|
+
This gem provides rudimentary support for pushing a given git repo to be deployed
|
71
|
+
as the app, by simplying providing the directory of the repo:
|
72
|
+
|
73
|
+
app.push("path/to/repository")
|
74
|
+
# => true
|
75
|
+
|
76
|
+
### Account
|
77
|
+
|
78
|
+
#### Getting the account details
|
79
|
+
|
80
|
+
acc = Heroku::API.account
|
81
|
+
# => #<Heroku::Model::Account id="01234567-89ab-cdef-0123-456789abcdef", email="username@example.com">
|
82
|
+
|
83
|
+
#### Updating the account details
|
84
|
+
|
85
|
+
acc.email = "joe-bloggs@example.com"
|
86
|
+
acc.allow_tracking = false # Let third party tracking services track you.
|
87
|
+
acc.save
|
88
|
+
# => #<Heroku::Model::Account id="01234567-89ab-cdef-0123-456789abcdef", email="joe-bloggs@example.com">
|
89
|
+
|
90
|
+
#### Changing the account password
|
91
|
+
|
92
|
+
acc.update_password("new_password", "old_password")
|
93
|
+
# => true
|
94
|
+
|
95
|
+
OR
|
96
|
+
|
97
|
+
Heroku::API.update_password("new_password", "old_password")
|
98
|
+
# => true
|
99
|
+
|
100
|
+
### Rate Limits
|
101
|
+
|
102
|
+
You can check the number of requests left like so:
|
103
|
+
|
104
|
+
Heroku::API.account.rate_limits
|
105
|
+
# => 1200
|
106
|
+
|
107
|
+
OR
|
108
|
+
|
109
|
+
Heroku::API.rate_limits
|
110
|
+
# => 1200
|
111
|
+
|
112
|
+
### Regions
|
113
|
+
|
114
|
+
Find out the available regions with:
|
115
|
+
|
116
|
+
Heroku::API.regions
|
117
|
+
# => [{"created_at"=>"2012-11-21T21:44:16Z", "description"=>"United States", "id"=>"59accabd-516d-4f0e-83e6-6e3757701145", "name"=>"us", "updated_at"=>"2013-04-05T10:13:06Z"}, {"created_at"=>"2012-11-21T22:05:26Z", "description"=>"Europe", "id"=>"ed30241c-ed8c-4bb6-9714-61953675d0b4", "name"=>"eu", "updated_at"=>"2013-04-05T07:07:28Z"}]
|
118
|
+
|
119
|
+
### Further API endpoints
|
120
|
+
|
121
|
+
The Heroku Platform API Gem does not currently support any further API end-points
|
122
|
+
natively. If you would like to add them, feel free to contribute, as directed below.
|
123
|
+
|
124
|
+
If you would like to test the various endpoints that are not currently fully
|
125
|
+
supported, please use the `Heroku::Conn` class, which will return the requested
|
126
|
+
data as Ruby Arrays and Hashes:
|
127
|
+
|
128
|
+
*Raw request for rate limit*
|
129
|
+
|
130
|
+
etag, response = Heroku::Conn::Get('/account/rate-limits'); response
|
131
|
+
# => { 'remaining' => '1200' }
|
132
|
+
|
133
|
+
For further information, visit the [Heroku Platform API](https://devcenter.heroku.com/articles/platform-api-reference).
|
134
|
+
|
135
|
+
## Contributing
|
136
|
+
|
137
|
+
1. Fork it
|
138
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
139
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
140
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
141
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/heroku.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'heroku/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "heroku-platform-api"
|
8
|
+
spec.version = Heroku::VERSION
|
9
|
+
spec.authors = ["Ashok Menon", "Rentify"]
|
10
|
+
spec.email = ["amenon94@gmail.com", "dev@rentify.com"]
|
11
|
+
spec.description = %q{Create, destroy and manage your heroku applications programmatically, using the Heroku Platform API.}
|
12
|
+
spec.summary = %q{Ruby client for the Heroku Platform API.}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "git"
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
spec.add_development_dependency "rspec"
|
26
|
+
end
|
data/lib/heroku/api.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'heroku/properties'
|
2
|
+
|
3
|
+
module Heroku
|
4
|
+
class API
|
5
|
+
require 'heroku/api/account'
|
6
|
+
require 'heroku/api/password'
|
7
|
+
require 'heroku/api/rate_limits'
|
8
|
+
require 'heroku/api/regions'
|
9
|
+
require 'heroku/api/apps'
|
10
|
+
require 'heroku/api/app'
|
11
|
+
|
12
|
+
extend Heroku::Properties::ConfigMethods
|
13
|
+
extend Heroku::API::Account
|
14
|
+
extend Heroku::API::Password
|
15
|
+
extend Heroku::API::RateLimits
|
16
|
+
extend Heroku::API::Regions
|
17
|
+
extend Heroku::API::Apps
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'heroku/conn'
|
2
|
+
require 'heroku/properties'
|
3
|
+
require 'heroku/model/account'
|
4
|
+
|
5
|
+
module Heroku
|
6
|
+
class API
|
7
|
+
module Account
|
8
|
+
@@etag = nil
|
9
|
+
RESOURCE_TYPE = "ACCOUNT"
|
10
|
+
|
11
|
+
def account
|
12
|
+
Heroku::Properties.logger.info("[Account] Fetching.")
|
13
|
+
|
14
|
+
@@etag, res =
|
15
|
+
Heroku::Conn::Get(
|
16
|
+
'/account',
|
17
|
+
etag: @@etag,
|
18
|
+
r_type: RESOURCE_TYPE
|
19
|
+
)
|
20
|
+
|
21
|
+
Heroku::Model::Account.new(res.merge("parent" => self))
|
22
|
+
end
|
23
|
+
|
24
|
+
def update_account(account)
|
25
|
+
Heroku::Properties.logger.info("[Account] Updating #{account.id}")
|
26
|
+
|
27
|
+
@@etag, res =
|
28
|
+
Heroku::Conn::Patch(
|
29
|
+
"/account",
|
30
|
+
r_type: RESOURCE_TYPE,
|
31
|
+
body: account.patchable.to_json
|
32
|
+
)
|
33
|
+
|
34
|
+
Heroku::Model::Account.new(res.merge("parent" => self))
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'heroku/conn'
|
2
|
+
require 'heroku/properties'
|
3
|
+
require 'heroku/model/app'
|
4
|
+
|
5
|
+
module Heroku
|
6
|
+
class API
|
7
|
+
module App
|
8
|
+
@@etags = {}
|
9
|
+
RESOURCE_TYPE = "APP"
|
10
|
+
|
11
|
+
def app(name_or_id)
|
12
|
+
Heroku::Properties.logger.info("[App] Fetching #{name_or_id}")
|
13
|
+
|
14
|
+
etag, res =
|
15
|
+
Heroku::Conn::Get(
|
16
|
+
"/apps/#{name_or_id}",
|
17
|
+
etag: @@etags[name_or_id],
|
18
|
+
r_type: RESOURCE_TYPE
|
19
|
+
)
|
20
|
+
|
21
|
+
@@etags[res['id']] = etag
|
22
|
+
@@etags[res['name']] = etag
|
23
|
+
Heroku::Model::App.new(res.merge("parent" => self))
|
24
|
+
end
|
25
|
+
|
26
|
+
def new(params = {})
|
27
|
+
Heroku::Properties.logger.info("[App] New with parameters: #{params.inspect}")
|
28
|
+
|
29
|
+
_, res =
|
30
|
+
Heroku::Conn::Post(
|
31
|
+
'/apps',
|
32
|
+
r_type: RESOURCE_TYPE,
|
33
|
+
body: params.to_json
|
34
|
+
)
|
35
|
+
|
36
|
+
Heroku::Model::App.new(res.merge("parent" => self))
|
37
|
+
end
|
38
|
+
|
39
|
+
def update_app(app)
|
40
|
+
Heroku::Properties.logger.info("[App] Updating #{app.id}")
|
41
|
+
|
42
|
+
etag, res =
|
43
|
+
Heroku::Conn::Patch(
|
44
|
+
app.end_point,
|
45
|
+
r_type: RESOURCE_TYPE,
|
46
|
+
body: app.patchable.to_json
|
47
|
+
)
|
48
|
+
|
49
|
+
@@etags[res['id']] = etag
|
50
|
+
@@etags[res['name']] = etag
|
51
|
+
Heroku::Model::App.new(res.merge("parent" => self))
|
52
|
+
end
|
53
|
+
|
54
|
+
def delete_app(app)
|
55
|
+
Heroku::Properties.logger.info("[App] Deleting #{app.id}")
|
56
|
+
Heroku::Conn::Delete(app.end_point, r_type: RESOURCE_TYPE)
|
57
|
+
true
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'heroku/conn'
|
2
|
+
require 'heroku/properties'
|
3
|
+
require 'heroku/model/app_list'
|
4
|
+
require 'heroku/model/app'
|
5
|
+
|
6
|
+
module Heroku
|
7
|
+
class API
|
8
|
+
module Apps
|
9
|
+
@@etag = nil
|
10
|
+
RESOURCE_TYPE = "APPS"
|
11
|
+
|
12
|
+
def apps
|
13
|
+
Heroku::Model::AppList.new( ->(parent){
|
14
|
+
Heroku::Properties.logger.info("[Apps] Fetching")
|
15
|
+
|
16
|
+
@@etag, res =
|
17
|
+
Heroku::Conn::Get(
|
18
|
+
"/apps",
|
19
|
+
etag: @@etag,
|
20
|
+
r_type: RESOURCE_TYPE
|
21
|
+
)
|
22
|
+
|
23
|
+
res.map do |params|
|
24
|
+
Heroku::Model::App.new(params.merge("parent" => parent))
|
25
|
+
end
|
26
|
+
})
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'heroku/conn'
|
2
|
+
require 'heroku/properties'
|
3
|
+
|
4
|
+
module Heroku
|
5
|
+
class API
|
6
|
+
module Password
|
7
|
+
def update_password(new_password, current_password)
|
8
|
+
Heroku::Properties.logger.info("[Password] Updating")
|
9
|
+
|
10
|
+
Heroku::Conn::Put("/account/password", body: {
|
11
|
+
password: new_password,
|
12
|
+
current_password: current_password
|
13
|
+
}.to_json)
|
14
|
+
true
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'heroku/conn'
|
2
|
+
require 'heroku/properties'
|
3
|
+
|
4
|
+
module Heroku
|
5
|
+
class API
|
6
|
+
module RateLimits
|
7
|
+
@@etag = nil
|
8
|
+
RESOURCE_TYPE = "RATE_LIMITS"
|
9
|
+
|
10
|
+
def rate_limits
|
11
|
+
Heroku::Properties.logger.info("[Rate Limits] Fetching")
|
12
|
+
|
13
|
+
@@etag, res =
|
14
|
+
Heroku::Conn::Get(
|
15
|
+
"/account/rate-limits",
|
16
|
+
etag: @@etag,
|
17
|
+
r_type: RESOURCE_TYPE
|
18
|
+
)
|
19
|
+
|
20
|
+
res["remaining"].to_i
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/heroku/conn.rb
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'net/http'
|
3
|
+
require 'heroku/properties'
|
4
|
+
|
5
|
+
module Heroku
|
6
|
+
class Conn
|
7
|
+
require 'heroku/conn/cache'
|
8
|
+
|
9
|
+
APIRequest = Struct.new(:method, :end_point)
|
10
|
+
|
11
|
+
@https = Net::HTTP.new('api.heroku.com', 443).tap do |https|
|
12
|
+
https.use_ssl = true
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.cache
|
16
|
+
@cache ||= Heroku::Conn::Cache.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.method_missing(method, end_point, opts = {})
|
20
|
+
_Request = Net::HTTP.const_get(method.capitalize)
|
21
|
+
|
22
|
+
req = _Request.new(end_point, headers(opts))
|
23
|
+
req.body = opts[:body]
|
24
|
+
api_req = APIRequest[method, end_point]
|
25
|
+
|
26
|
+
Heroku::Properties.logger.debug("[Conn] Attempting #{method.upcase} #{end_point} ...")
|
27
|
+
|
28
|
+
check_response(api_req, opts[:r_type], @https.request(req))
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def self.check_response(api_req, r_type, res)
|
34
|
+
Heroku::Properties.logger.debug("[Conn] Received #{res.code} for #{r_type} at #{api_req.end_point}")
|
35
|
+
|
36
|
+
case res
|
37
|
+
when Net::HTTPOK,
|
38
|
+
Net::HTTPCreated
|
39
|
+
cache.put(
|
40
|
+
r_type, res["ETag"],
|
41
|
+
JSON.parse(res.body)
|
42
|
+
)
|
43
|
+
when Net::HTTPPartialContent
|
44
|
+
cache.put(
|
45
|
+
r_type, res["ETag"],
|
46
|
+
gather_partial_content(api_req, res)
|
47
|
+
)
|
48
|
+
when Net::HTTPNotModified then cache.fetch(r_type, res["ETag"])
|
49
|
+
when Net::HTTPSuccess then [res["ETag"], JSON.parse(res.body)]
|
50
|
+
else raise_exception(res)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def gather_partial_content(api_req, res)
|
55
|
+
Heroku::Properties.logger.info("[Conn] Gathering Partial Content.")
|
56
|
+
|
57
|
+
list_head = JSON.parse(res.body)
|
58
|
+
etag, list_tail =
|
59
|
+
self.send(
|
60
|
+
api_req.method,
|
61
|
+
api_req.end_point,
|
62
|
+
range: res["Next-Range"]
|
63
|
+
)
|
64
|
+
|
65
|
+
list_tail.unshift(*list_head)
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.raise_exception(res)
|
69
|
+
Heroku::Properties.logger.error("[Conn] Uh oh, something went wrong with request #{res["Request-Id"]}.")
|
70
|
+
raise res.class::EXCEPTION_TYPE.new(status(res.code), nil)
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.status(code)
|
74
|
+
Hash[Net::HTTPResponse::CODE_TO_OBJ.map { |k, v| [k, v.to_s] }]
|
75
|
+
.merge({ "429" => "Net::HTTPTooManyRequests" }) # Ruby 1.9.3 shiv
|
76
|
+
.fetch(code, "Net::HTTPUnknownError")
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.headers(opts = {})
|
80
|
+
{
|
81
|
+
"Accept" => 'application/vnd.heroku+json; version=3',
|
82
|
+
"Content-Type" => 'application/json',
|
83
|
+
"Authorization" => Heroku::Properties.auth_token,
|
84
|
+
"User-Agent" => Heroku::Properties::USER_AGENT
|
85
|
+
}.merge({}.tap do |header|
|
86
|
+
header["If-None-Match"] = opts[:etag] if opts[:etag]
|
87
|
+
header["Range"] = opts[:range] if opts[:range]
|
88
|
+
end)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'heroku/properties'
|
2
|
+
|
3
|
+
class Heroku::Conn::Cache
|
4
|
+
CachePair = Struct.new(:response, :etag)
|
5
|
+
|
6
|
+
def initialize()
|
7
|
+
@response_cache = {}
|
8
|
+
@etag_pointers = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def put(r_type, new_etag, json)
|
12
|
+
pair = pair(r_type)
|
13
|
+
key = key(json)
|
14
|
+
old_etag, _ = pair.response[key]
|
15
|
+
record = [new_etag, json]
|
16
|
+
|
17
|
+
Heroku::Properties.logger.debug("[#{r_type} Cache] Caching #{key} #{new_etag}")
|
18
|
+
Heroku::Properties.logger.debug("[#{r_type} Cache] Dissociating tag: #{old_etag}")
|
19
|
+
|
20
|
+
pair.etag.delete(old_etag)
|
21
|
+
pair.response[key] = record
|
22
|
+
pair.etag[new_etag] = record
|
23
|
+
record
|
24
|
+
end
|
25
|
+
|
26
|
+
def fetch(r_type, etag)
|
27
|
+
Heroku::Properties.logger.info("[#{r_type} Cache] Fetching #{etag}")
|
28
|
+
pair(r_type).etag[etag]
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def pair(r_type)
|
34
|
+
CachePair[
|
35
|
+
@response_cache[r_type] ||= {},
|
36
|
+
@etag_pointers[r_type] ||= {}
|
37
|
+
]
|
38
|
+
end
|
39
|
+
|
40
|
+
def key(json_response)
|
41
|
+
case json_response
|
42
|
+
when Array then "list"
|
43
|
+
when Hash then json_response['id']
|
44
|
+
else nil
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/heroku/model.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'heroku/api/password'
|
2
|
+
require 'heroku/api/rate_limits'
|
3
|
+
require 'heroku/model/model_helper'
|
4
|
+
|
5
|
+
module Heroku
|
6
|
+
module Model
|
7
|
+
class Account < Struct.new(
|
8
|
+
:parent,
|
9
|
+
:id,
|
10
|
+
:email,
|
11
|
+
:verified,
|
12
|
+
:allow_tracking,
|
13
|
+
:beta,
|
14
|
+
:last_login,
|
15
|
+
:updated_at,
|
16
|
+
:created_at
|
17
|
+
)
|
18
|
+
|
19
|
+
include Heroku::Model::ModelHelper
|
20
|
+
include Heroku::API::Password
|
21
|
+
include Heroku::API::RateLimits
|
22
|
+
|
23
|
+
def inspect
|
24
|
+
"#<#{self.class.name} #{identifier}>"
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize(params = {})
|
28
|
+
super(*struct_init_from_hash(params))
|
29
|
+
end
|
30
|
+
|
31
|
+
def patchable
|
32
|
+
sub_struct_as_hash(:email, :allow_tracking)
|
33
|
+
end
|
34
|
+
|
35
|
+
def identifiable
|
36
|
+
sub_struct_as_hash(:id, :email)
|
37
|
+
end
|
38
|
+
|
39
|
+
def save
|
40
|
+
parent.update_account(self)
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'heroku/model/model_helper'
|
2
|
+
require 'git'
|
3
|
+
|
4
|
+
module Heroku
|
5
|
+
module Model
|
6
|
+
class App < Struct.new(
|
7
|
+
:parent,
|
8
|
+
:id,
|
9
|
+
:name,
|
10
|
+
:owner,
|
11
|
+
:region,
|
12
|
+
:git_url,
|
13
|
+
:web_url,
|
14
|
+
:repo_size,
|
15
|
+
:slug_size,
|
16
|
+
:buildpack_provided_description,
|
17
|
+
:stack,
|
18
|
+
:maintenance,
|
19
|
+
:archived_at,
|
20
|
+
:created_at,
|
21
|
+
:released_at,
|
22
|
+
:updated_at
|
23
|
+
)
|
24
|
+
|
25
|
+
include Heroku::Model::ModelHelper
|
26
|
+
|
27
|
+
def inspect
|
28
|
+
"#<#{self.class.name} #{identifier}>"
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize(params = {})
|
32
|
+
super(*struct_init_from_hash(params))
|
33
|
+
end
|
34
|
+
|
35
|
+
def push(dir)
|
36
|
+
begin
|
37
|
+
Git.open(dir, log: Heroku::Properties.logger).push(git_url)
|
38
|
+
true
|
39
|
+
rescue => e
|
40
|
+
Heroku::Properties.logger.error(e.message)
|
41
|
+
e.backtrace.each do |line|
|
42
|
+
Heroku::Properties.logger.error(line)
|
43
|
+
end
|
44
|
+
|
45
|
+
false
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def patchable
|
50
|
+
sub_struct_as_hash(:maintenance, :name)
|
51
|
+
end
|
52
|
+
|
53
|
+
def identifiable
|
54
|
+
sub_struct_as_hash(:id, :name)
|
55
|
+
end
|
56
|
+
|
57
|
+
def end_point
|
58
|
+
"/apps/#{id}"
|
59
|
+
end
|
60
|
+
|
61
|
+
def save
|
62
|
+
parent.update_app(self)
|
63
|
+
end
|
64
|
+
|
65
|
+
def destroy
|
66
|
+
parent.delete_app(self)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'heroku/api/app'
|
2
|
+
require 'heroku/model/array_proxy'
|
3
|
+
|
4
|
+
module Heroku
|
5
|
+
module Model
|
6
|
+
class AppList < Heroku::Model::ArrayProxy
|
7
|
+
include Heroku::API::App
|
8
|
+
|
9
|
+
def inspect
|
10
|
+
"#<Heroku::Model::Apps>"
|
11
|
+
end
|
12
|
+
|
13
|
+
def [](key)
|
14
|
+
case key
|
15
|
+
when String, Symbol then app(key.to_s)
|
16
|
+
else super(key)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Heroku
|
2
|
+
module Model
|
3
|
+
class ArrayProxy
|
4
|
+
def initialize(deferred_array)
|
5
|
+
@deferred_array = deferred_array
|
6
|
+
end
|
7
|
+
|
8
|
+
def method_missing(sym, *args)
|
9
|
+
begin
|
10
|
+
proxy_array.send(sym, *args)
|
11
|
+
rescue NoMethodError
|
12
|
+
super
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def all
|
17
|
+
proxy_array
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
def proxy_array
|
23
|
+
@proxy_array ||= @deferred_array.call(self)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Heroku
|
2
|
+
module Model
|
3
|
+
module ModelHelper
|
4
|
+
def struct_init_from_hash(hash)
|
5
|
+
hash.values_at(*members.map(&:to_s))
|
6
|
+
end
|
7
|
+
|
8
|
+
def sub_struct_as_hash(*params)
|
9
|
+
Hash[(params & members).map { |k| [k, send(k)] }]
|
10
|
+
end
|
11
|
+
|
12
|
+
def identifier
|
13
|
+
identifiable.to_a.map { |k,v| "#{k}=#{v.inspect}"}.join(', ')
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'heroku/version'
|
3
|
+
|
4
|
+
module Heroku
|
5
|
+
class Properties
|
6
|
+
require 'heroku/properties/null_logger'
|
7
|
+
|
8
|
+
USER_AGENT = "Heroku Platform API Gem #{Heroku::VERSION}"
|
9
|
+
@@auth_token = nil
|
10
|
+
@@logger = nil
|
11
|
+
|
12
|
+
def self.auth_token
|
13
|
+
@@auth_token
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.api_key=(key)
|
17
|
+
raise ArgumentError, "Need an API key" if key.nil?
|
18
|
+
@@auth_token = Base64.strict_encode64(":#{key}\n").strip
|
19
|
+
key
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.logger
|
23
|
+
@@logger || NullLogger.new
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.logger=(logger)
|
27
|
+
@@logger = logger
|
28
|
+
end
|
29
|
+
|
30
|
+
module ConfigMethods
|
31
|
+
def configure # yield
|
32
|
+
yield Heroku::Properties
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/heroku_api.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Heroku::Conn::Cache do
|
4
|
+
subject { Heroku::Conn::Cache.new }
|
5
|
+
|
6
|
+
describe "#put" do
|
7
|
+
let(:new_etag) { "new_etag" }
|
8
|
+
let(:old_etag) { "old_etag" }
|
9
|
+
let(:r_type) { "test" }
|
10
|
+
let(:json) { { a: 1 } }
|
11
|
+
let(:new_json) { { a: 2 } }
|
12
|
+
|
13
|
+
before do
|
14
|
+
subject.put(r_type, old_etag, json)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should update the cache value" do
|
18
|
+
subject.put(r_type, new_etag, new_json)
|
19
|
+
expect(subject.fetch(r_type, new_etag)).to eq([new_etag, new_json])
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should remove references to the old etag." do
|
23
|
+
subject.put(r_type, new_etag, new_json)
|
24
|
+
|
25
|
+
expect(subject.fetch(r_type, old_etag)).to be_nil
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Heroku::Conn do
|
4
|
+
|
5
|
+
describe ".check_response" do
|
6
|
+
context "when the response is unsuccessful" do
|
7
|
+
let(:unsuccessful_response) { Net::HTTPClientError.new() }
|
8
|
+
|
9
|
+
it "should raise a error" do
|
10
|
+
expect { described_class.send(:check_response, nil, nil, response) }.to raise_error
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Heroku::Model::ModelHelper do
|
4
|
+
let(:_Model) do
|
5
|
+
Class.new(Struct.new(:a, :b, :c)) do
|
6
|
+
include Heroku::Model::ModelHelper
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
subject { _Model[1, 2, 3] }
|
11
|
+
|
12
|
+
|
13
|
+
describe "#struct_init_from_hash" do
|
14
|
+
let(:valid_hash) { { "a" => 1, "b" => 2, "c" => 3 } }
|
15
|
+
let(:disorderd_hash) { { "b" => 2, "a" => 1, "c" => 3 }}
|
16
|
+
let(:invalid_hash) { { "d" => 4 } }
|
17
|
+
|
18
|
+
it "finds all the values from the hash that are members of the Model." do
|
19
|
+
init_list = subject.struct_init_from_hash(valid_hash)
|
20
|
+
expect(init_list).to match_array([1, 2, 3])
|
21
|
+
end
|
22
|
+
|
23
|
+
it "provides the values in the Model's order, not the hash's." do
|
24
|
+
init_list = subject.struct_init_from_hash(disorderd_hash)
|
25
|
+
expect(init_list).to eq([1, 2, 3])
|
26
|
+
end
|
27
|
+
|
28
|
+
it "ignores values from the has that are not members of the Model." do
|
29
|
+
init_list = subject.struct_init_from_hash(invalid_hash)
|
30
|
+
expect(init_list).not_to include(4)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "#sub_struct_as_hash" do
|
35
|
+
let(:valid_params) { [:a, :b] }
|
36
|
+
let(:invalid_params) { [:d] }
|
37
|
+
|
38
|
+
it "returns a hash of the requisite parameters." do
|
39
|
+
hash = subject.sub_struct_as_hash(*valid_params)
|
40
|
+
expect(hash).to include(a: 1, b: 2)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "does not include paramters that are not requested." do
|
44
|
+
hash = subject.sub_struct_as_hash(*valid_params)
|
45
|
+
expect(hash).not_to include(:c)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "ignores parameters that do not exist." do
|
49
|
+
hash = subject.sub_struct_as_hash(*invalid_params)
|
50
|
+
expect(hash).not_to include(:d)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "#identifier" do
|
55
|
+
context "when there are no identifiable parameters" do
|
56
|
+
before { subject.should_receive(:identifiable).and_return({}) }
|
57
|
+
its(:identifier) { should == "" }
|
58
|
+
end
|
59
|
+
|
60
|
+
context "when the identifier is one parameter" do
|
61
|
+
before { subject.should_receive(:identifiable).and_return({ a: 1 }) }
|
62
|
+
its(:identifier) { should == "a=1" }
|
63
|
+
end
|
64
|
+
|
65
|
+
context "when the identifier contains multiple parameters" do
|
66
|
+
before { subject.should_receive(:identifiable).and_return({ a: 1, b: 2}) }
|
67
|
+
its(:identifier) { should == "a=1, b=2" }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Heroku::Properties::NullLogger do
|
4
|
+
|
5
|
+
subject { described_class.new }
|
6
|
+
|
7
|
+
it { should respond_to(:unknown).with(1).argument }
|
8
|
+
it { should respond_to(:warn).with(1).argument }
|
9
|
+
it { should respond_to(:debug).with(1).argument }
|
10
|
+
it { should respond_to(:info).with(1).argument }
|
11
|
+
|
12
|
+
describe "#tagged" do
|
13
|
+
let(:block_check) { double("block_check", call: true) }
|
14
|
+
|
15
|
+
it { should respond_to(:tagged).with(1).argument }
|
16
|
+
it "calls the block provided to it" do
|
17
|
+
block_check.should_receive(:call).once
|
18
|
+
|
19
|
+
subject.tagged("tag") do
|
20
|
+
block_check.call
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Heroku::Properties do
|
4
|
+
|
5
|
+
it "should respond to #auth_token" do
|
6
|
+
expect(described_class).to respond_to(:auth_token)
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "#api_key=" do
|
10
|
+
let(:api_key) { "01234567-89ab-cdef-0123-456789abcdef" }
|
11
|
+
|
12
|
+
it "should create a base64 encoded auth token" do
|
13
|
+
described_class.api_key = api_key
|
14
|
+
expect(described_class.auth_token).to eq(Base64.encode64(":#{api_key}\n").strip)
|
15
|
+
end
|
16
|
+
|
17
|
+
context "when no api_key is provided" do
|
18
|
+
let(:api_key) { nil }
|
19
|
+
|
20
|
+
it "should raise an error" do
|
21
|
+
expect { described_class.api_key = api_key }.to raise_error(ArgumentError)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should leave the auth_token unchanged" do
|
25
|
+
expect {
|
26
|
+
begin
|
27
|
+
described_class.api_key = api_key
|
28
|
+
rescue ArgumentError
|
29
|
+
end
|
30
|
+
}.not_to change(described_class, :auth_token)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context "when no logger is provided" do
|
36
|
+
before do
|
37
|
+
described_class.logger = nil
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should provide a null logger" do
|
41
|
+
expect(described_class.logger).not_to be_nil
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
Bundler.setup
|
2
|
+
|
3
|
+
require 'heroku_api'
|
4
|
+
|
5
|
+
RSpec.configure do |config|
|
6
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
7
|
+
config.run_all_when_everything_filtered = true
|
8
|
+
config.filter_run :focus
|
9
|
+
|
10
|
+
# Run specs in random order to surface order dependencies. If you find an
|
11
|
+
# order dependency and want to debug it, you can fix the order by providing
|
12
|
+
# the seed, which is printed after each run.
|
13
|
+
# --seed 1234
|
14
|
+
config.order = 'random'
|
15
|
+
end
|
metadata
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: heroku-platform-api
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ashok Menon
|
8
|
+
- Rentify
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-08-30 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: git
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ! '>='
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '0'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ! '>='
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '0'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: bundler
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ~>
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '1.3'
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ~>
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '1.3'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: rake
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ! '>='
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ! '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: rspec
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ! '>='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
type: :development
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
description: Create, destroy and manage your heroku applications programmatically,
|
71
|
+
using the Heroku Platform API.
|
72
|
+
email:
|
73
|
+
- amenon94@gmail.com
|
74
|
+
- dev@rentify.com
|
75
|
+
executables: []
|
76
|
+
extensions: []
|
77
|
+
extra_rdoc_files: []
|
78
|
+
files:
|
79
|
+
- .gitignore
|
80
|
+
- .rspec
|
81
|
+
- Gemfile
|
82
|
+
- LICENSE.txt
|
83
|
+
- README.md
|
84
|
+
- Rakefile
|
85
|
+
- heroku.gemspec
|
86
|
+
- lib/heroku/api.rb
|
87
|
+
- lib/heroku/api/account.rb
|
88
|
+
- lib/heroku/api/app.rb
|
89
|
+
- lib/heroku/api/apps.rb
|
90
|
+
- lib/heroku/api/password.rb
|
91
|
+
- lib/heroku/api/rate_limits.rb
|
92
|
+
- lib/heroku/api/regions.rb
|
93
|
+
- lib/heroku/conn.rb
|
94
|
+
- lib/heroku/conn/cache.rb
|
95
|
+
- lib/heroku/model.rb
|
96
|
+
- lib/heroku/model/account.rb
|
97
|
+
- lib/heroku/model/app.rb
|
98
|
+
- lib/heroku/model/app_list.rb
|
99
|
+
- lib/heroku/model/array_proxy.rb
|
100
|
+
- lib/heroku/model/model_helper.rb
|
101
|
+
- lib/heroku/properties.rb
|
102
|
+
- lib/heroku/properties/null_logger.rb
|
103
|
+
- lib/heroku/version.rb
|
104
|
+
- lib/heroku_api.rb
|
105
|
+
- spec/heroku/conn/cache_spec.rb
|
106
|
+
- spec/heroku/conn_spec.rb
|
107
|
+
- spec/heroku/model/model_helper_spec.rb
|
108
|
+
- spec/heroku/properties/null_logger_spec.rb
|
109
|
+
- spec/heroku/properties_spec.rb
|
110
|
+
- spec/spec_helper.rb
|
111
|
+
homepage: ''
|
112
|
+
licenses:
|
113
|
+
- MIT
|
114
|
+
metadata: {}
|
115
|
+
post_install_message:
|
116
|
+
rdoc_options: []
|
117
|
+
require_paths:
|
118
|
+
- lib
|
119
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - ! '>='
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '0'
|
124
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
125
|
+
requirements:
|
126
|
+
- - ! '>='
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
version: '0'
|
129
|
+
requirements: []
|
130
|
+
rubyforge_project:
|
131
|
+
rubygems_version: 2.0.6
|
132
|
+
signing_key:
|
133
|
+
specification_version: 4
|
134
|
+
summary: Ruby client for the Heroku Platform API.
|
135
|
+
test_files:
|
136
|
+
- spec/heroku/conn/cache_spec.rb
|
137
|
+
- spec/heroku/conn_spec.rb
|
138
|
+
- spec/heroku/model/model_helper_spec.rb
|
139
|
+
- spec/heroku/properties/null_logger_spec.rb
|
140
|
+
- spec/heroku/properties_spec.rb
|
141
|
+
- spec/spec_helper.rb
|