heroku-platform-api 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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=
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in heroku.gemspec
4
+ gemspec
@@ -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.
@@ -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
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -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
@@ -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
@@ -0,0 +1,13 @@
1
+ require 'heroku/conn'
2
+ require 'heroku/properties'
3
+
4
+ module Heroku
5
+ class API
6
+ module Regions
7
+ def regions
8
+ Heroku::Properties.logger.info("[Regions] Fetching")
9
+ Heroku::Conn::Get("/regions").last
10
+ end
11
+ end
12
+ end
13
+ end
@@ -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
@@ -0,0 +1,9 @@
1
+ module Heroku
2
+ module Model
3
+ require 'heroku/model/model_helper'
4
+ require 'heroku/model/array_proxy'
5
+ require 'heroku/model/account'
6
+ require 'heroku/model/app_list'
7
+ require 'heroku/model/app'
8
+ end
9
+ end
@@ -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
@@ -0,0 +1,16 @@
1
+ module Heroku
2
+ class Properties
3
+ class NullLogger
4
+
5
+ def info( msg); end
6
+ def warn( msg); end
7
+ def debug( msg); end
8
+ def unknown(msg); end
9
+
10
+ def tagged(tag) # yields
11
+ yield
12
+ end
13
+
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,3 @@
1
+ module Heroku
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,6 @@
1
+ module Heroku
2
+ require 'heroku/properties'
3
+ require 'heroku/conn'
4
+ require 'heroku/model'
5
+ require 'heroku/api'
6
+ end
@@ -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
@@ -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