cfoundry 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,140 @@
1
+ require "json"
2
+ require "base64"
3
+
4
+ require "cfoundry/baseclient"
5
+ require "cfoundry/uaaclient"
6
+
7
+ require "cfoundry/errors"
8
+
9
+ module CFoundry::V2
10
+ class Base < CFoundry::BaseClient
11
+ attr_accessor :target, :token, :proxy, :trace
12
+
13
+ def initialize(
14
+ target = "https://api.cloudfoundry.com",
15
+ token = nil)
16
+ @target = target
17
+ @token = token
18
+ end
19
+
20
+
21
+ # invalidate token data when changing token
22
+ def token=(t)
23
+ @token = t
24
+ end
25
+
26
+
27
+ # The UAA used for this client.
28
+ #
29
+ # `false` if no UAA (legacy)
30
+ def uaa
31
+ return @uaa unless @uaa.nil?
32
+
33
+ endpoint = info[:authorization_endpoint]
34
+ return @uaa = false unless endpoint
35
+
36
+ @uaa = CFoundry::UAAClient.new(endpoint)
37
+ @uaa.trace = @trace
38
+ @uaa.token = @token
39
+ @uaa
40
+ end
41
+
42
+
43
+ # Cloud metadata
44
+ def info
45
+ get("info", nil => :json)
46
+ end
47
+
48
+
49
+ [:app, :organization, :app_space, :service, :service_instance,
50
+ :user, :runtime, :framework].each do |obj|
51
+ plural = "#{obj}s"
52
+
53
+ define_method(obj) do |guid, *args|
54
+ depth, _ = args
55
+ depth ||= 1
56
+
57
+ params = { :"inline-relations-depth" => depth }
58
+
59
+ get("v2", plural, guid, nil => :json, :params => params)
60
+ end
61
+
62
+ define_method(:"create_#{obj}") do |payload|
63
+ post(payload, "v2", plural, :json => :json)
64
+ end
65
+
66
+ define_method(:"delete_#{obj}") do |guid|
67
+ delete("v2", plural, guid, nil => nil)
68
+ true
69
+ end
70
+
71
+ define_method(:"update_#{obj}") do |guid, payload|
72
+ put(payload, "v2", plural, guid, :json => :json)
73
+ end
74
+
75
+ define_method(plural) do |*args|
76
+ depth, query = args
77
+ depth ||= 1
78
+
79
+ params = { :"inline-relations-depth" => depth }
80
+
81
+ if query
82
+ params[:q] = "#{query.keys.first}:#{query.values.first}"
83
+ end
84
+
85
+ get("v2", plural, nil => :json, :params => params)
86
+ end
87
+ end
88
+
89
+ alias :spaces :app_spaces
90
+ alias :space :app_space
91
+ alias :update_space :update_app_space
92
+ alias :delete_space :delete_app_space
93
+ alias :create_space :create_app_space
94
+
95
+ private
96
+
97
+ def handle_response(response, accept)
98
+ json = accept == :json
99
+
100
+ case response.code
101
+ when 200, 201, 204, 302
102
+ if accept == :headers
103
+ return response.headers
104
+ end
105
+
106
+ if json
107
+ if response.code == 204
108
+ raise "Expected JSON response, got 204 No Content"
109
+ end
110
+
111
+ parse_json(response)
112
+ else
113
+ response
114
+ end
115
+
116
+ when 400
117
+ info = parse_json(response)
118
+ raise CFoundry::APIError.new(info[:code], info[:description])
119
+
120
+ when 401
121
+ info = parse_json(response)
122
+ raise CFoundry::Denied.new(info[:code], info[:description])
123
+
124
+ when 404
125
+ raise CFoundry::NotFound
126
+
127
+ when 411, 500, 504
128
+ begin
129
+ info = parse_json(response)
130
+ raise CFoundry::APIError.new(info[:code], info[:description])
131
+ rescue JSON::ParserError
132
+ raise CFoundry::BadResponse.new(response.code, response)
133
+ end
134
+
135
+ else
136
+ raise CFoundry::BadResponse.new(response.code, response)
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,184 @@
1
+ require "cfoundry/v2/base"
2
+
3
+ require "cfoundry/v2/app"
4
+ require "cfoundry/v2/framework"
5
+ require "cfoundry/v2/organization"
6
+ require "cfoundry/v2/runtime"
7
+ require "cfoundry/v2/service"
8
+ require "cfoundry/v2/service_instance"
9
+ require "cfoundry/v2/space"
10
+ require "cfoundry/v2/user"
11
+
12
+ module CFoundry::V2
13
+ # The primary API entrypoint. Wraps a BaseClient to provide nicer return
14
+ # values. Initialize with the target and, optionally, an auth token. These
15
+ # are the only two internal states.
16
+ class Client
17
+ # Internal BaseClient instance. Normally won't be touching this.
18
+ attr_reader :base
19
+
20
+ # [Organization] Currently targeted organization.
21
+ attr_accessor :current_organization
22
+
23
+ # [Space] Currently targeted space.
24
+ attr_accessor :current_space
25
+
26
+
27
+ # Create a new Client for interfacing with the given target.
28
+ #
29
+ # A token may also be provided to skip the login step.
30
+ def initialize(target = "http://api.cloudfoundry.com", token = nil)
31
+ @base = Base.new(target, token)
32
+ end
33
+
34
+ # The current target URL of the client.
35
+ def target
36
+ @base.target
37
+ end
38
+
39
+ # Current proxy user. Usually nil.
40
+ def proxy
41
+ @base.proxy
42
+ end
43
+
44
+ # Set the proxy user for the client. Must be authorized as an
45
+ # administrator for this to have any effect.
46
+ def proxy=(email)
47
+ @base.proxy = email
48
+ end
49
+
50
+ # Is the client tracing API requests?
51
+ def trace
52
+ @base.trace
53
+ end
54
+
55
+ # Set the tracing flag; if true, API requests and responses will be
56
+ # printed out.
57
+ def trace=(bool)
58
+ @base.trace = bool
59
+ end
60
+
61
+ # The currently authenticated user.
62
+ def current_user
63
+ if user = info[:user]
64
+ user(user)
65
+ end
66
+ end
67
+
68
+ # Cloud metadata
69
+ def info
70
+ @base.info
71
+ end
72
+
73
+ # Login prompts
74
+ def login_prompts
75
+ if @base.uaa
76
+ @base.uaa.prompts
77
+ else
78
+ { :username => ["text", "Email"],
79
+ :password => ["password", "Password"]
80
+ }
81
+ end
82
+ end
83
+
84
+ # Authenticate with the target. Sets the client token.
85
+ #
86
+ # Credentials is a hash, typically containing :username and :password
87
+ # keys.
88
+ #
89
+ # The values in the hash should mirror the prompts given by
90
+ # `login_prompts`.
91
+ def login(credentials)
92
+ @current_organization = nil
93
+ @current_space = nil
94
+
95
+ @base.token =
96
+ if @base.uaa
97
+ @base.uaa.authorize(credentials)
98
+ else
99
+ @base.create_token(
100
+ { :password => credentials[:password] },
101
+ credentials[:username])[:token]
102
+ end
103
+ end
104
+
105
+ # Clear client token. No requests are made for this.
106
+ def logout
107
+ @base.token = nil
108
+ end
109
+
110
+ # Is an authentication token set on the client?
111
+ def logged_in?
112
+ !!@base.token
113
+ end
114
+
115
+
116
+ [:app, :organization, :app_space, :service, :service_binding,
117
+ :service_instance, :user, :runtime, :framework].each do |singular|
118
+ klass = singular.to_s.capitalize.gsub(/(.)_(.)/) do
119
+ $1 + $2.upcase
120
+ end
121
+
122
+ plural = :"#{singular}s"
123
+
124
+ define_method(singular) do |*args|
125
+ id, _ = args
126
+ CFoundry::V2.const_get(klass).new(id, self)
127
+ end
128
+
129
+ define_method(plural) do |*args|
130
+ depth, query = args
131
+ depth ||= 1
132
+
133
+ @base.send(plural, depth, query)[:resources].collect do |json|
134
+ send(:"make_#{singular}", json)
135
+ end
136
+ end
137
+
138
+ define_method(:"#{singular}_from") do |path|
139
+ uri = URI.parse(path)
140
+
141
+ if uri.query
142
+ uri.query += "&inline-relations-depth=1"
143
+ else
144
+ uri.query = "inline-relations-depth=1"
145
+ end
146
+
147
+ send(
148
+ :"make_#{singular}",
149
+ @base.request_path(
150
+ :get,
151
+ uri.to_s,
152
+ nil => :json))
153
+ end
154
+
155
+ define_method(:"#{plural}_from") do |path|
156
+ uri = URI.parse(path)
157
+
158
+ if uri.query
159
+ uri.query += "&inline-relations-depth=1"
160
+ else
161
+ uri.query = "inline-relations-depth=1"
162
+ end
163
+
164
+ @base.request_path(
165
+ :get,
166
+ uri.to_s,
167
+ nil => :json)[:resources].collect do |json|
168
+ send(:"make_#{singular}", json)
169
+ end
170
+ end
171
+
172
+ define_method(:"make_#{singular}") do |json|
173
+ CFoundry::V2.const_get(klass).new(json[:metadata][:guid], self, json)
174
+ end
175
+ end
176
+
177
+ alias :spaces :app_spaces
178
+ alias :space :app_space
179
+
180
+ alias :system_frameworks :frameworks
181
+ alias :system_runtimes :runtimes
182
+ alias :system_services :services
183
+ end
184
+ end
@@ -0,0 +1,8 @@
1
+ require "cfoundry/v2/model"
2
+
3
+ module CFoundry::V2
4
+ class Domain < Model
5
+ attribute :name
6
+ to_one :organization
7
+ end
8
+ end
@@ -0,0 +1,18 @@
1
+ require "cfoundry/v2/model"
2
+
3
+ module CFoundry::V2
4
+ class Framework < Model
5
+ attribute :name
6
+ attribute :description
7
+ to_many :apps
8
+
9
+ def detection
10
+ nil # TODO for v2?
11
+ end
12
+
13
+ def runtimes
14
+ [] # TODO for v2?
15
+ end
16
+ end
17
+ end
18
+
@@ -0,0 +1,149 @@
1
+ module CFoundry::V2
2
+ class Model
3
+ class << self
4
+ def defaults
5
+ @defaults ||= {}
6
+ end
7
+
8
+ def attribute(name, opts = {})
9
+ default = opts[:default] || nil
10
+ defaults[name] = default if default
11
+
12
+ define_method(name) {
13
+ manifest[:entity][name] || default
14
+ }
15
+
16
+ define_method(:"#{name}=") { |val|
17
+ @manifest ||= {}
18
+ @manifest[:entity] ||= {}
19
+ @manifest[:entity][name] = val
20
+ }
21
+ end
22
+
23
+ def to_one(name, opts = {})
24
+ obj = opts[:as] || name
25
+
26
+ define_method(name) {
27
+ if manifest[:entity].key? name
28
+ @client.send(:"make_#{obj}", manifest[:entity][name])
29
+ else
30
+ @client.send(:"#{name}_from", send("#{obj}_url"))
31
+ end
32
+ }
33
+
34
+ define_method(:"#{name}_url") {
35
+ manifest[:entity][:"#{name}_url"]
36
+ }
37
+
38
+ define_method(:"#{name}=") { |x|
39
+ @manifest ||= {}
40
+ @manifest[:entity] ||= {}
41
+ @manifest[:entity][:"#{name}_guid"] = x.id
42
+ }
43
+ end
44
+
45
+ def to_many(plural, opts = {})
46
+ singular = plural.to_s.sub(/s$/, "").to_sym
47
+ object = opts[:as] || singular
48
+
49
+ define_method(plural) {
50
+ if manifest[:entity].key? plural
51
+ manifest[:entity][plural].collect do |json|
52
+ @client.send(:"make_#{object}", json)
53
+ end
54
+ else
55
+ @client.send(:"#{plural}_from", send("#{plural}_url"))
56
+ end
57
+ }
58
+
59
+ define_method(:"#{plural}_url") {
60
+ manifest[:entity][:"#{plural}_url"]
61
+ }
62
+
63
+ # TODO: these are hacky
64
+ define_method(:"add_#{singular}") { |x|
65
+ @client.base.request_path(
66
+ :put,
67
+ ["v2", "#{object_name}s", @id, plural, x.id],
68
+ nil => :json)
69
+ }
70
+
71
+ define_method(:"remove_#{singular}") {
72
+ @client.base.request_path(
73
+ :delete,
74
+ ["v2", "#{object_name}s", @id, plural, x.id],
75
+ nil => :json)
76
+ }
77
+
78
+ define_method(:"#{plural}=") { |xs|
79
+ @manifest ||= {}
80
+ @manifest[:entity] ||= {}
81
+ @manifest[:entity][:"#{singular}_guids"] = xs.collect(&:id)
82
+ }
83
+ end
84
+ end
85
+
86
+ attr_reader :id
87
+
88
+ def initialize(id, client, manifest = nil)
89
+ @id = id
90
+ @client = client
91
+ @manifest = manifest
92
+ end
93
+
94
+ def manifest
95
+ # inline depth of 2 for fewer requests
96
+ @manifest ||= @client.base.send(object_name, @id, 2)
97
+ end
98
+
99
+ def inspect
100
+ "\#<#{self.class.name} '#@id'>"
101
+ end
102
+
103
+ def object_name
104
+ @object_name ||=
105
+ self.class.name.split("::").last.gsub(
106
+ /([a-z])([A-Z])/,
107
+ '\1_\2').downcase
108
+ end
109
+
110
+ def create!
111
+ @manifest =
112
+ @client.base.send(
113
+ :"create_#{object_name}",
114
+ @manifest[:entity].merge(self.class.defaults))
115
+
116
+ @id = @manifest[:metadata][:guid]
117
+
118
+ true
119
+ end
120
+
121
+ def update!(diff = nil)
122
+ @client.base.send(
123
+ :"update_#{object_name}",
124
+ @id,
125
+ diff || manifest[:entity])
126
+
127
+ @manifest = nil
128
+ end
129
+
130
+ def delete!
131
+ @client.base.send(:"delete_#{object_name}", @id)
132
+
133
+ if @manifest
134
+ @manifest.delete :metadata
135
+ end
136
+ end
137
+
138
+ def exists?
139
+ @client.base.send(object_name, @id)
140
+ true
141
+ rescue CFoundry::APIError # TODO: NotFound would be better
142
+ false
143
+ end
144
+
145
+ def ==(other)
146
+ other.is_a?(self.class) && @id == other.id
147
+ end
148
+ end
149
+ end