cfoundry-IronFoundry 0.3.34
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +746 -0
- data/Rakefile +40 -0
- data/lib/cfoundry.rb +2 -0
- data/lib/cfoundry/baseclient.rb +235 -0
- data/lib/cfoundry/chatty_hash.rb +34 -0
- data/lib/cfoundry/client.rb +25 -0
- data/lib/cfoundry/errors.rb +106 -0
- data/lib/cfoundry/uaaclient.rb +111 -0
- data/lib/cfoundry/upload_helpers.rb +100 -0
- data/lib/cfoundry/v1/app.rb +562 -0
- data/lib/cfoundry/v1/base.rb +209 -0
- data/lib/cfoundry/v1/client.rb +232 -0
- data/lib/cfoundry/v1/framework.rb +21 -0
- data/lib/cfoundry/v1/runtime.rb +20 -0
- data/lib/cfoundry/v1/service.rb +25 -0
- data/lib/cfoundry/v1/service_instance.rb +112 -0
- data/lib/cfoundry/v1/user.rb +89 -0
- data/lib/cfoundry/v2/app.rb +328 -0
- data/lib/cfoundry/v2/base.rb +175 -0
- data/lib/cfoundry/v2/client.rb +198 -0
- data/lib/cfoundry/v2/domain.rb +8 -0
- data/lib/cfoundry/v2/framework.rb +12 -0
- data/lib/cfoundry/v2/model.rb +268 -0
- data/lib/cfoundry/v2/organization.rb +13 -0
- data/lib/cfoundry/v2/route.rb +9 -0
- data/lib/cfoundry/v2/runtime.rb +9 -0
- data/lib/cfoundry/v2/service.rb +17 -0
- data/lib/cfoundry/v2/service_auth_token.rb +9 -0
- data/lib/cfoundry/v2/service_binding.rb +8 -0
- data/lib/cfoundry/v2/service_instance.rb +10 -0
- data/lib/cfoundry/v2/service_plan.rb +10 -0
- data/lib/cfoundry/v2/space.rb +14 -0
- data/lib/cfoundry/v2/user.rb +58 -0
- data/lib/cfoundry/version.rb +4 -0
- data/lib/cfoundry/zip.rb +56 -0
- data/spec/Rakefile +14 -0
- data/spec/client_spec.rb +206 -0
- data/spec/helpers.rb +29 -0
- metadata +169 -0
data/Rakefile
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require "rake"
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
|
4
|
+
require "cfoundry/version"
|
5
|
+
|
6
|
+
task :default => "spec"
|
7
|
+
|
8
|
+
desc "Run specs"
|
9
|
+
task "spec" => ["bundler:install", "test:spec"]
|
10
|
+
|
11
|
+
task :build do
|
12
|
+
sh "gem build cfoundry.gemspec"
|
13
|
+
end
|
14
|
+
|
15
|
+
task :install => :build do
|
16
|
+
sh "gem install --local cfoundry-#{CFoundry::VERSION}"
|
17
|
+
end
|
18
|
+
|
19
|
+
task :uninstall do
|
20
|
+
sh "gem uninstall cfoundry"
|
21
|
+
end
|
22
|
+
|
23
|
+
task :reinstall => [:uninstall, :install]
|
24
|
+
|
25
|
+
task :release => :build do
|
26
|
+
sh "gem push cfoundry-#{CFoundry::VERSION}.gem"
|
27
|
+
end
|
28
|
+
|
29
|
+
namespace "bundler" do
|
30
|
+
desc "Install gems"
|
31
|
+
task "install" do
|
32
|
+
sh("bundle install")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
namespace "test" do
|
37
|
+
task "spec" do |t|
|
38
|
+
sh("cd spec && bundle exec rake spec")
|
39
|
+
end
|
40
|
+
end
|
data/lib/cfoundry.rb
ADDED
@@ -0,0 +1,235 @@
|
|
1
|
+
require "restclient"
|
2
|
+
require "multi_json"
|
3
|
+
|
4
|
+
module CFoundry
|
5
|
+
class BaseClient # :nodoc:
|
6
|
+
attr_accessor :trace, :no_backtrace
|
7
|
+
|
8
|
+
def initialize(target, token = nil)
|
9
|
+
@target = target
|
10
|
+
@token = token
|
11
|
+
@trace = false
|
12
|
+
@no_backtrace = false
|
13
|
+
end
|
14
|
+
|
15
|
+
def request_path(method, path, types = {}, options = {})
|
16
|
+
path = url(path) if path.is_a?(Array)
|
17
|
+
|
18
|
+
unless types.empty?
|
19
|
+
if params = types.delete(:params)
|
20
|
+
options[:params] = params
|
21
|
+
end
|
22
|
+
|
23
|
+
if types.size > 1
|
24
|
+
raise "request types must contain only one Content-Type => Accept"
|
25
|
+
end
|
26
|
+
|
27
|
+
options[:type] = types.keys.first
|
28
|
+
options[:accept] = types.values.first
|
29
|
+
end
|
30
|
+
|
31
|
+
request(method, path, options)
|
32
|
+
end
|
33
|
+
|
34
|
+
# grab the metadata from a token that looks like:
|
35
|
+
#
|
36
|
+
# bearer (base64 ...)
|
37
|
+
def token_data
|
38
|
+
tok = Base64.decode64(@token.sub(/^bearer\s+/, ""))
|
39
|
+
tok.sub!(/\{.+?\}/, "") # clear algo
|
40
|
+
MultiJson.load(tok[/\{.+?\}/], :symbolize_keys => true)
|
41
|
+
|
42
|
+
# normally i don't catch'em all, but can't expect all tokens to be the
|
43
|
+
# proper format, so just silently fail as this is not critical
|
44
|
+
rescue
|
45
|
+
{}
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def parse_json(x)
|
51
|
+
MultiJson.load(x, :symbolize_keys => true)
|
52
|
+
end
|
53
|
+
|
54
|
+
def request(method, path, options = {})
|
55
|
+
accept = options.delete(:accept)
|
56
|
+
type = options.delete(:type)
|
57
|
+
payload = options.delete(:payload)
|
58
|
+
params = options.delete(:params)
|
59
|
+
|
60
|
+
headers = {}
|
61
|
+
headers["Authorization"] = @token if @token
|
62
|
+
headers["Proxy-User"] = @proxy if @proxy
|
63
|
+
|
64
|
+
if accept_type = mimetype(accept)
|
65
|
+
headers["Accept"] = accept_type
|
66
|
+
end
|
67
|
+
|
68
|
+
if content_type = mimetype(type)
|
69
|
+
headers["Content-Type"] = content_type
|
70
|
+
end
|
71
|
+
|
72
|
+
unless payload.is_a?(String)
|
73
|
+
case type
|
74
|
+
when :json
|
75
|
+
payload = MultiJson.dump(payload)
|
76
|
+
when :form
|
77
|
+
payload = encode_params(payload)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
headers["Content-Length"] = payload ? payload.size : 0
|
82
|
+
|
83
|
+
headers.merge!(options[:headers]) if options[:headers]
|
84
|
+
|
85
|
+
if params
|
86
|
+
uri = URI.parse(path)
|
87
|
+
path += (uri.query ? "&" : "?") + encode_params(params)
|
88
|
+
end
|
89
|
+
|
90
|
+
req = options.dup
|
91
|
+
req[:method] = method
|
92
|
+
req[:url] = @target + path
|
93
|
+
req[:headers] = headers
|
94
|
+
req[:payload] = payload
|
95
|
+
|
96
|
+
json = accept == :json
|
97
|
+
|
98
|
+
RestClient::Request.execute(req) do |response, request|
|
99
|
+
print_trace(req, request, response, caller) if @trace
|
100
|
+
handle_response(response, accept)
|
101
|
+
end
|
102
|
+
rescue SocketError, Errno::ECONNREFUSED => e
|
103
|
+
raise TargetRefused, e.message
|
104
|
+
end
|
105
|
+
|
106
|
+
def mimetype(type)
|
107
|
+
case type
|
108
|
+
when String
|
109
|
+
type
|
110
|
+
when :json
|
111
|
+
"application/json"
|
112
|
+
when :form
|
113
|
+
"application/x-www-form-urlencoded"
|
114
|
+
when nil
|
115
|
+
nil
|
116
|
+
# return request headers (not really Accept)
|
117
|
+
when :headers
|
118
|
+
nil
|
119
|
+
else
|
120
|
+
raise "unknown mimetype #{type.inspect}"
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def encode_params(hash, escape = true)
|
125
|
+
hash.keys.map do |k|
|
126
|
+
v = hash[k]
|
127
|
+
v = MultiJson.dump(v) if v.is_a?(Hash)
|
128
|
+
v = URI.escape(v.to_s, /[^#{URI::PATTERN::UNRESERVED}]/) if escape
|
129
|
+
"#{k}=#{v}"
|
130
|
+
end.join("&")
|
131
|
+
end
|
132
|
+
|
133
|
+
def request_with_types(method, path, options = {})
|
134
|
+
if path.last.is_a?(Hash)
|
135
|
+
types = path.pop
|
136
|
+
end
|
137
|
+
|
138
|
+
request_path(method, url(path), types || {}, options)
|
139
|
+
end
|
140
|
+
|
141
|
+
def get(*path)
|
142
|
+
request_with_types(:get, path)
|
143
|
+
end
|
144
|
+
|
145
|
+
def delete(*path)
|
146
|
+
request_with_types(:delete, path)
|
147
|
+
end
|
148
|
+
|
149
|
+
def post(payload, *path)
|
150
|
+
request_with_types(:post, path, :payload => payload)
|
151
|
+
end
|
152
|
+
|
153
|
+
def put(payload, *path)
|
154
|
+
request_with_types(:put, path, :payload => payload)
|
155
|
+
end
|
156
|
+
|
157
|
+
def url(segments)
|
158
|
+
"/#{safe_path(segments)}"
|
159
|
+
end
|
160
|
+
|
161
|
+
def safe_path(*segments)
|
162
|
+
segments.flatten.collect { |x|
|
163
|
+
URI.encode x.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]")
|
164
|
+
}.join("/")
|
165
|
+
end
|
166
|
+
|
167
|
+
def print_trace(req, request, response, locs)
|
168
|
+
$stderr.puts ">>>"
|
169
|
+
$stderr.puts "PROXY: #{RestClient.proxy}" if RestClient.proxy
|
170
|
+
$stderr.puts "REQUEST: #{req[:method]} #{req[:url]}"
|
171
|
+
$stderr.puts "RESPONSE_HEADERS:"
|
172
|
+
response.headers.each do |key, value|
|
173
|
+
$stderr.puts " #{key} : #{value}"
|
174
|
+
end
|
175
|
+
$stderr.puts "REQUEST_HEADERS:"
|
176
|
+
request.headers.each do |key, value|
|
177
|
+
$stderr.puts " #{key} : #{value}"
|
178
|
+
end
|
179
|
+
$stderr.puts "REQUEST_BODY: #{req[:payload]}" if req[:payload]
|
180
|
+
$stderr.puts "RESPONSE: [#{response.code}]"
|
181
|
+
begin
|
182
|
+
parsed_body = MultiJson.load(response.body)
|
183
|
+
$stderr.puts MultiJson.dump(parsed_body, :pretty => true)
|
184
|
+
rescue
|
185
|
+
$stderr.puts "#{response.body}"
|
186
|
+
end
|
187
|
+
$stderr.puts "<<<"
|
188
|
+
|
189
|
+
return if @no_backtrace
|
190
|
+
|
191
|
+
interesting_locs = locs.drop_while { |loc|
|
192
|
+
loc =~ /\/(cfoundry\/|restclient\/|net\/http)/
|
193
|
+
}
|
194
|
+
|
195
|
+
$stderr.puts "--- backtrace:"
|
196
|
+
|
197
|
+
$stderr.puts "... (boring)" unless locs == interesting_locs
|
198
|
+
|
199
|
+
trimmed_locs = interesting_locs[0..5]
|
200
|
+
|
201
|
+
trimmed_locs.each do |loc|
|
202
|
+
$stderr.puts "=== #{loc}"
|
203
|
+
end
|
204
|
+
|
205
|
+
$stderr.puts "... (trimmed)" unless trimmed_locs == interesting_locs
|
206
|
+
end
|
207
|
+
|
208
|
+
def handle_response(response, accept)
|
209
|
+
json = accept == :json
|
210
|
+
|
211
|
+
case response.code
|
212
|
+
when 200, 201, 204, 302
|
213
|
+
if accept == :headers
|
214
|
+
return response.headers
|
215
|
+
end
|
216
|
+
|
217
|
+
if json
|
218
|
+
if response.code == 204
|
219
|
+
raise "Expected JSON response, got 204 No Content"
|
220
|
+
end
|
221
|
+
|
222
|
+
parse_json(response)
|
223
|
+
else
|
224
|
+
response
|
225
|
+
end
|
226
|
+
|
227
|
+
when 404
|
228
|
+
raise CFoundry::NotFound
|
229
|
+
|
230
|
+
else
|
231
|
+
raise CFoundry::BadResponse.new(response.code, response)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module CFoundry
|
2
|
+
class ChattyHash
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
def initialize(callback, hash = {})
|
6
|
+
@callback = callback
|
7
|
+
@hash = hash
|
8
|
+
end
|
9
|
+
|
10
|
+
def [](name)
|
11
|
+
@hash[name]
|
12
|
+
end
|
13
|
+
|
14
|
+
def []=(name, value)
|
15
|
+
@hash[name] = value
|
16
|
+
@callback.call(self)
|
17
|
+
value
|
18
|
+
end
|
19
|
+
|
20
|
+
def each(&blk)
|
21
|
+
@hash.each(&blk)
|
22
|
+
end
|
23
|
+
|
24
|
+
def delete(key)
|
25
|
+
value = @hash.delete(key)
|
26
|
+
@callback.call(self)
|
27
|
+
value
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_json(*args)
|
31
|
+
@hash.to_json(*args)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require "cfoundry/baseclient"
|
2
|
+
|
3
|
+
require "cfoundry/v1/client"
|
4
|
+
require "cfoundry/v2/client"
|
5
|
+
|
6
|
+
module CFoundry
|
7
|
+
class Client < BaseClient
|
8
|
+
def self.new(*args)
|
9
|
+
target, _ = args
|
10
|
+
|
11
|
+
base = super(target)
|
12
|
+
|
13
|
+
case base.info[:version]
|
14
|
+
when 2
|
15
|
+
CFoundry::V2::Client.new(*args)
|
16
|
+
else
|
17
|
+
CFoundry::V1::Client.new(*args)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def info
|
22
|
+
get("info", nil => :json)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module CFoundry
|
2
|
+
# Exception representing errors returned by the API.
|
3
|
+
class APIError < RuntimeError
|
4
|
+
class << self
|
5
|
+
# Generic error code for the exception.
|
6
|
+
attr_reader :error_code
|
7
|
+
|
8
|
+
# Generic description for the exception.
|
9
|
+
attr_reader :description
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def setup(code, description = nil)
|
14
|
+
@error_code = code
|
15
|
+
@description = description
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Create an APIError with a given error code and description.
|
20
|
+
def initialize(error_code = nil, description = nil)
|
21
|
+
@error_code = error_code
|
22
|
+
@description = description
|
23
|
+
end
|
24
|
+
|
25
|
+
# A number representing the error.
|
26
|
+
def error_code
|
27
|
+
@error_code || self.class.error_code
|
28
|
+
end
|
29
|
+
|
30
|
+
# A description of the error.
|
31
|
+
def description
|
32
|
+
@description || self.class.description
|
33
|
+
end
|
34
|
+
|
35
|
+
# Exception message.
|
36
|
+
def to_s
|
37
|
+
if error_code
|
38
|
+
"#{error_code}: #{description}"
|
39
|
+
else
|
40
|
+
description
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Generic exception thrown when accessing something that doesn't exist (e.g.
|
46
|
+
# getting info of unknown application).
|
47
|
+
class NotFound < APIError
|
48
|
+
setup(404, "entity not found or inaccessible")
|
49
|
+
end
|
50
|
+
|
51
|
+
# Lower-level exception for when we cannot connect to the target.
|
52
|
+
class TargetRefused < APIError
|
53
|
+
@description = "target refused connection"
|
54
|
+
|
55
|
+
# Error message.
|
56
|
+
attr_reader :message
|
57
|
+
|
58
|
+
# Message varies as this represents various network errors.
|
59
|
+
def initialize(message)
|
60
|
+
@message = message
|
61
|
+
end
|
62
|
+
|
63
|
+
# Exception message.
|
64
|
+
def to_s
|
65
|
+
"#{description} (#{@message})"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Exception raised when an application payload fails to upload.
|
70
|
+
class UploadFailed < APIError
|
71
|
+
setup(402)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Exception raised when access is denied to something, either because the
|
75
|
+
# user is not logged in or is not an administrator.
|
76
|
+
class Denied < APIError
|
77
|
+
# Specific error code.
|
78
|
+
attr_reader :error_code
|
79
|
+
|
80
|
+
# Specific description.
|
81
|
+
attr_reader :description
|
82
|
+
|
83
|
+
# Initialize, with a default error code and message.
|
84
|
+
def initialize(
|
85
|
+
error_code = 200,
|
86
|
+
description = "Operation not permitted")
|
87
|
+
@error_code = error_code
|
88
|
+
@description = description
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Exception raised when the response is unexpected; usually from a server
|
93
|
+
# error.
|
94
|
+
class BadResponse < StandardError
|
95
|
+
# Initialize, with the HTTP response code and body.
|
96
|
+
def initialize(code, body = nil)
|
97
|
+
@code = code
|
98
|
+
@body = body
|
99
|
+
end
|
100
|
+
|
101
|
+
# Exception message.
|
102
|
+
def to_s
|
103
|
+
"target failed to handle our request due to an internal error (#{@code})"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|