cfoundry 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -1,11 +1,31 @@
1
- require 'rake'
2
- require "bundler/gem_tasks"
1
+ require "rake"
2
+
3
+ $LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
4
+ require "cfoundry/version"
3
5
 
4
6
  task :default => "spec"
5
7
 
6
8
  desc "Run specs"
7
9
  task "spec" => ["bundler:install", "test:spec"]
8
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
+
9
29
  namespace "bundler" do
10
30
  desc "Install gems"
11
31
  task "install" do
@@ -0,0 +1,206 @@
1
+ require "restclient"
2
+ require "json"
3
+
4
+ module CFoundry
5
+ class BaseClient # :nodoc:
6
+ def initialize(target, token = nil)
7
+ @target = target
8
+ @token = token
9
+ end
10
+
11
+ def request_path(method, path, types = {}, options = {})
12
+ path = url(path) if path.is_a?(Array)
13
+
14
+ unless types.empty?
15
+ if params = types.delete(:params)
16
+ options[:params] = params
17
+ end
18
+
19
+ if types.size > 1
20
+ raise "request types must contain only one Content-Type => Accept"
21
+ end
22
+
23
+ options[:type] = types.keys.first
24
+ options[:accept] = types.values.first
25
+ end
26
+
27
+ request(method, path, options)
28
+ end
29
+
30
+ private
31
+
32
+ def parse_json(x)
33
+ JSON.parse(x, :symbolize_names => true)
34
+ end
35
+
36
+ def request(method, path, options = {})
37
+ accept = options.delete(:accept)
38
+ type = options.delete(:type)
39
+ payload = options.delete(:payload)
40
+ params = options.delete(:params)
41
+
42
+ headers = {}
43
+ headers["Authorization"] = @token if @token
44
+ headers["Proxy-User"] = @proxy if @proxy
45
+
46
+ if accept_type = mimetype(accept)
47
+ headers["Accept"] = accept_type
48
+ end
49
+
50
+ if content_type = mimetype(type)
51
+ headers["Content-Type"] = content_type
52
+ end
53
+
54
+ unless payload.is_a?(String)
55
+ case type
56
+ when :json
57
+ payload = payload.to_json
58
+ when :form
59
+ payload = encode_params(payload, false)
60
+ end
61
+ end
62
+
63
+ headers["Content-Length"] = payload ? payload.size : 0
64
+
65
+ headers.merge!(options[:headers]) if options[:headers]
66
+
67
+ if params
68
+ path += "?" + encode_params(params)
69
+ end
70
+
71
+ req = options.dup
72
+ req[:method] = method
73
+ req[:url] = @target + path
74
+ req[:headers] = headers
75
+ req[:payload] = payload
76
+
77
+ json = accept == :json
78
+
79
+ RestClient::Request.execute(req) do |response, request|
80
+ if @trace
81
+ puts '>>>'
82
+ puts "PROXY: #{RestClient.proxy}" if RestClient.proxy
83
+ puts "REQUEST: #{req[:method]} #{req[:url]}"
84
+ puts "RESPONSE_HEADERS:"
85
+ response.headers.each do |key, value|
86
+ puts " #{key} : #{value}"
87
+ end
88
+ puts "REQUEST_HEADERS:"
89
+ request.headers.each do |key, value|
90
+ puts " #{key} : #{value}"
91
+ end
92
+ puts "REQUEST_BODY: #{req[:payload]}" if req[:payload]
93
+ puts "RESPONSE: [#{response.code}]"
94
+ begin
95
+ puts JSON.pretty_generate(JSON.parse(response.body))
96
+ rescue
97
+ puts "#{response.body}"
98
+ end
99
+ puts '<<<'
100
+ end
101
+
102
+ handle_response(response, accept)
103
+ end
104
+ rescue SocketError, Errno::ECONNREFUSED => e
105
+ raise TargetRefused, e.message
106
+ end
107
+
108
+ def mimetype(type)
109
+ case type
110
+ when String
111
+ type
112
+ when :json
113
+ "application/json"
114
+ when :form
115
+ "application/x-www-form-urlencoded"
116
+ when nil
117
+ nil
118
+ # return request headers (not really Accept)
119
+ when :headers
120
+ nil
121
+ else
122
+ raise "unknown mimetype #{type.inspect}"
123
+ end
124
+ end
125
+
126
+ def encode_params(hash, escape = true)
127
+ hash.keys.map do |k|
128
+ v = hash[k]
129
+
130
+ value =
131
+ if v.is_a?(Hash)
132
+ v.to_json
133
+ elsif escape
134
+ URI.escape(v.to_s, /[^#{URI::PATTERN::UNRESERVED}]/)
135
+ else
136
+ v
137
+ end
138
+
139
+ "#{k}=#{value}"
140
+ end.join("&")
141
+ end
142
+
143
+ def request_with_types(method, path, options = {})
144
+ if path.last.is_a?(Hash)
145
+ types = path.pop
146
+ end
147
+
148
+ request_path(method, url(path), types || {}, options)
149
+ end
150
+
151
+ def get(*path)
152
+ request_with_types(:get, path)
153
+ end
154
+
155
+ def delete(*path)
156
+ request_with_types(:delete, path)
157
+ end
158
+
159
+ def post(payload, *path)
160
+ request_with_types(:post, path, :payload => payload)
161
+ end
162
+
163
+ def put(payload, *path)
164
+ request_with_types(:put, path, :payload => payload)
165
+ end
166
+
167
+ def url(segments)
168
+ "/#{safe_path(segments)}"
169
+ end
170
+
171
+ def safe_path(*segments)
172
+ segments.flatten.collect { |x|
173
+ URI.encode x.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]")
174
+ }.join("/")
175
+ end
176
+
177
+ private
178
+
179
+ def handle_response(response, accept)
180
+ json = accept == :json
181
+
182
+ case response.code
183
+ when 200, 201, 204, 302
184
+ if accept == :headers
185
+ return response.headers
186
+ end
187
+
188
+ if json
189
+ if response.code == 204
190
+ raise "Expected JSON response, got 204 No Content"
191
+ end
192
+
193
+ parse_json(response)
194
+ else
195
+ response
196
+ end
197
+
198
+ when 404
199
+ raise CFoundry::NotFound
200
+
201
+ else
202
+ raise CFoundry::BadResponse.new(response.code, response)
203
+ end
204
+ end
205
+ end
206
+ end
@@ -1,155 +1,25 @@
1
- require "cfoundry/restclient"
2
- require "cfoundry/app"
3
- require "cfoundry/service"
4
- require "cfoundry/user"
1
+ require "cfoundry/baseclient"
5
2
 
3
+ require "cfoundry/v1/client"
4
+ require "cfoundry/v2/client"
6
5
 
7
6
  module CFoundry
8
- # The primary API entrypoint. Wraps RESTClient to provide nicer return
9
- # values. Initialize with the target and, optionally, an auth token. These
10
- # are the only two internal states.
11
- class Client
12
- # Internal RESTClient instance. Normally won't be touching this.
13
- attr_reader :rest
7
+ class Client < BaseClient
8
+ def self.new(*args)
9
+ target, _ = args
14
10
 
15
- # Create a new Client for interfacing with the given target.
16
- #
17
- # A token may also be provided to skip the login step.
18
- def initialize(target = "http://api.cloudfoundry.com", token = nil)
19
- @rest = RESTClient.new(target, token)
20
- end
21
-
22
- # The current target URL of the client.
23
- def target
24
- @rest.target
25
- end
26
-
27
- # Current proxy user. Usually nil.
28
- def proxy
29
- @rest.proxy
30
- end
31
-
32
- # Set the proxy user for the client. Must be authorized as an
33
- # administrator for this to have any effect.
34
- def proxy=(email)
35
- @rest.proxy = email
36
- end
37
-
38
- # Is the client tracing API requests?
39
- def trace
40
- @rest.trace
41
- end
42
-
43
- # Set the tracing flag; if true, API requests and responses will be
44
- # printed out.
45
- def trace=(bool)
46
- @rest.trace = bool
47
- end
48
-
49
-
50
- # Retrieve target metadata.
51
- def info
52
- @rest.info
53
- end
54
-
55
- # Retrieve available services. Returned as a Hash from vendor => metadata.
56
- def system_services
57
- services = {}
58
-
59
- @rest.system_services.each do |type, vendors|
60
- vendors.each do |vendor, versions|
61
- services[vendor] =
62
- { :type => type,
63
- :versions => versions.keys,
64
- :description => versions.values[0]["description"],
65
- :vendor => vendor
66
- }
67
- end
68
- end
69
-
70
- services
71
- end
72
-
73
- # Retrieve available runtimes.
74
- def system_runtimes
75
- @rest.system_runtimes
76
- end
11
+ base = super(target)
77
12
 
78
-
79
- # Retrieve user list. Admin-only.
80
- def users
81
- @rest.users.collect do |json|
82
- CFoundry::User.new(
83
- json["email"],
84
- self,
85
- { "email" => json["email"],
86
- "admin" => json["admin"] })
87
- end
88
- end
89
-
90
- # Construct a User object. The return value is lazy, and no requests are
91
- # made from this alone.
92
- #
93
- # This should be used for both user creation (after calling User#create!)
94
- # and retrieval.
95
- def user(email)
96
- CFoundry::User.new(email, self)
97
- end
98
-
99
- # Create a user on the target and return a User object representing them.
100
- def register(email, password)
101
- @rest.create_user(:email => email, :password => password)
102
- user(email)
103
- end
104
-
105
- # Authenticate with the target. Sets the client token.
106
- def login(email, password)
107
- @rest.token =
108
- @rest.create_token({ :password => password }, email)["token"]
109
- end
110
-
111
- # Clear client token. No requests are made for this.
112
- def logout
113
- @rest.token = nil
114
- end
115
-
116
- # Is an authentication token set on the client?
117
- def logged_in?
118
- !!@rest.token
119
- end
120
-
121
-
122
- # Retreive all of the current user's applications.
123
- def apps
124
- @rest.apps.collect do |json|
125
- CFoundry::App.new(json["name"], self, json)
13
+ case base.info[:version]
14
+ when 2
15
+ CFoundry::V2::Client.new(*args)
16
+ else
17
+ CFoundry::V1::Client.new(*args)
126
18
  end
127
19
  end
128
20
 
129
- # Construct an App object. The return value is lazy, and no requests are
130
- # made from this method alone.
131
- #
132
- # This should be used for both app creation (after calling App#create!)
133
- # and retrieval.
134
- def app(name)
135
- CFoundry::App.new(name, self)
136
- end
137
-
138
-
139
- # Retrieve all of the current user's services.
140
- def services
141
- @rest.services.collect do |json|
142
- CFoundry::Service.new(json["name"], self, json)
143
- end
144
- end
145
-
146
- # Construct a Service object. The return value is lazy, and no requests are
147
- # made from this method alone.
148
- #
149
- # This should be used for both service creation (after calling
150
- # Service#create!) and retrieval.
151
- def service(name)
152
- CFoundry::Service.new(name, self)
21
+ def info
22
+ get("info", nil => :json)
153
23
  end
154
24
  end
155
25
  end
@@ -0,0 +1,103 @@
1
+ require "cfoundry/baseclient"
2
+
3
+ module CFoundry
4
+ class UAAClient < BaseClient
5
+ attr_accessor :target, :client_id, :scope, :redirect_uri, :token, :trace
6
+
7
+ def initialize(
8
+ target = "https://uaa.cloudfoundry.com",
9
+ client_id = "vmc")
10
+ @target = target
11
+ @client_id = client_id
12
+ @scope = ["read"]
13
+ @redirect_uri = "http://uaa.cloudfoundry.com/redirect/vmc"
14
+ end
15
+
16
+ def prompts
17
+ get("login", nil => :json)[:prompts]
18
+ end
19
+
20
+ def authorize(credentials)
21
+ query = {
22
+ :client_id => @client_id,
23
+ :scope => Array(@scope).join(" "),
24
+ :response_type => "token",
25
+ :redirect_uri => @redirect_uri
26
+ }
27
+
28
+ extract_token(
29
+ post(
30
+ { :credentials => credentials },
31
+ "oauth", "authorize",
32
+ :form => :headers,
33
+ :params => query)[:location])
34
+ end
35
+
36
+ def users
37
+ get("Users", nil => :json)
38
+ end
39
+
40
+ private
41
+
42
+ def handle_response(response, accept)
43
+ json = accept == :json
44
+
45
+ case response.code
46
+ when 200, 204, 302
47
+ if accept == :headers
48
+ return response.headers
49
+ end
50
+
51
+ if json
52
+ if response.code == 204
53
+ raise "Expected JSON response, got 204 No Content"
54
+ end
55
+
56
+ parse_json(response)
57
+ else
58
+ response
59
+ end
60
+
61
+ when 400, 403
62
+ info = parse_json(response)
63
+ raise Denied.new(403, info[:error_description])
64
+
65
+ when 401
66
+ info = parse_json(response)
67
+ raise Denied.new(401, info[:error_description])
68
+
69
+ when 404
70
+ raise NotFound
71
+
72
+ when 409
73
+ info = parse_json(response)
74
+ raise CFoundry::Denied.new(409, info[:message])
75
+
76
+ when 411, 500, 504
77
+ begin
78
+ raise_error(parse_json(response))
79
+ rescue JSON::ParserError
80
+ raise BadResponse.new(response.code, response)
81
+ end
82
+
83
+ else
84
+ raise BadResponse.new(response.code, response)
85
+ end
86
+ end
87
+
88
+ def extract_token(url)
89
+ _, params = url.split('#')
90
+ return unless params
91
+
92
+ values = {}
93
+ params.split("&").each do |pair|
94
+ key, val = pair.split("=")
95
+ values[key] = val
96
+ end
97
+
98
+ return unless values["access_token"] && values["token_type"]
99
+
100
+ "#{values["token_type"]} #{values["access_token"]}"
101
+ end
102
+ end
103
+ end