heroku-platform-api 0.0.1

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.
@@ -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