cfoundry 0.2.2 → 0.3.0

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