cfoundry-IronFoundry 0.3.34

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.
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,2 @@
1
+ require "cfoundry/version"
2
+ require "cfoundry/client"
@@ -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