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/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
@@ -0,0 +1,175 @@
|
|
1
|
+
require "multi_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, :space, :user, :runtime, :framework, :service,
|
50
|
+
:domain, :route, :service_plan, :service_binding, :service_instance,
|
51
|
+
:service_auth_token
|
52
|
+
].each do |obj|
|
53
|
+
plural = "#{obj}s"
|
54
|
+
|
55
|
+
define_method(obj) do |guid, *args|
|
56
|
+
depth, _ = args
|
57
|
+
depth ||= 1
|
58
|
+
|
59
|
+
params = { :"inline-relations-depth" => depth }
|
60
|
+
|
61
|
+
get("v2", plural, guid, nil => :json, :params => params)
|
62
|
+
end
|
63
|
+
|
64
|
+
define_method(:"create_#{obj}") do |payload|
|
65
|
+
post(payload, "v2", plural, :json => :json)
|
66
|
+
end
|
67
|
+
|
68
|
+
define_method(:"delete_#{obj}") do |guid|
|
69
|
+
delete("v2", plural, guid, nil => nil)
|
70
|
+
true
|
71
|
+
end
|
72
|
+
|
73
|
+
define_method(:"update_#{obj}") do |guid, payload|
|
74
|
+
put(payload, "v2", plural, guid, :json => :json)
|
75
|
+
end
|
76
|
+
|
77
|
+
define_method(plural) do |*args|
|
78
|
+
get("v2", plural, nil => :json, :params => params_from(args))
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def resource_match(fingerprints)
|
83
|
+
put(fingerprints, "v2", "resource_match", :json => :json)
|
84
|
+
end
|
85
|
+
|
86
|
+
def upload_app(guid, zipfile, resources = [])
|
87
|
+
payload = {
|
88
|
+
:resources => MultiJson.dump(resources),
|
89
|
+
:multipart => true,
|
90
|
+
:application =>
|
91
|
+
if zipfile.is_a? File
|
92
|
+
zipfile
|
93
|
+
elsif zipfile.is_a? String
|
94
|
+
File.new(zipfile, "rb")
|
95
|
+
end
|
96
|
+
}
|
97
|
+
|
98
|
+
put(payload, "v2", "apps", guid, "bits")
|
99
|
+
rescue RestClient::ServerBrokeConnection
|
100
|
+
retry
|
101
|
+
end
|
102
|
+
|
103
|
+
def files(guid, instance, *path)
|
104
|
+
get("v2", "apps", guid, "instances", instance, "files", *path)
|
105
|
+
end
|
106
|
+
alias :file :files
|
107
|
+
|
108
|
+
def instances(guid)
|
109
|
+
get("v2", "apps", guid, "instances", nil => :json)
|
110
|
+
end
|
111
|
+
|
112
|
+
def stats(guid)
|
113
|
+
get("v2", "apps", guid, "stats", nil => :json)
|
114
|
+
end
|
115
|
+
|
116
|
+
|
117
|
+
def params_from(args)
|
118
|
+
depth, query = args
|
119
|
+
depth ||= 1
|
120
|
+
|
121
|
+
params = { :"inline-relations-depth" => depth }
|
122
|
+
|
123
|
+
if query
|
124
|
+
params[:q] = "#{query.keys.first}:#{query.values.first}"
|
125
|
+
end
|
126
|
+
|
127
|
+
params
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
def handle_response(response, accept)
|
133
|
+
json = accept == :json
|
134
|
+
|
135
|
+
case response.code
|
136
|
+
when 200, 201, 204, 302
|
137
|
+
if accept == :headers
|
138
|
+
return response.headers
|
139
|
+
end
|
140
|
+
|
141
|
+
if json
|
142
|
+
if response.code == 204
|
143
|
+
raise "Expected JSON response, got 204 No Content"
|
144
|
+
end
|
145
|
+
|
146
|
+
parse_json(response)
|
147
|
+
else
|
148
|
+
response
|
149
|
+
end
|
150
|
+
|
151
|
+
when 400
|
152
|
+
info = parse_json(response)
|
153
|
+
raise CFoundry::APIError.new(info[:code], info[:description])
|
154
|
+
|
155
|
+
when 401, 403
|
156
|
+
info = parse_json(response)
|
157
|
+
raise CFoundry::Denied.new(info[:code], info[:description])
|
158
|
+
|
159
|
+
when 404
|
160
|
+
raise CFoundry::NotFound
|
161
|
+
|
162
|
+
when 411, 500, 504
|
163
|
+
begin
|
164
|
+
info = parse_json(response)
|
165
|
+
raise CFoundry::APIError.new(info[:code], info[:description])
|
166
|
+
rescue MultiJson::DecodeError
|
167
|
+
raise CFoundry::BadResponse.new(response.code, response)
|
168
|
+
end
|
169
|
+
|
170
|
+
else
|
171
|
+
raise CFoundry::BadResponse.new(response.code, response)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
@@ -0,0 +1,198 @@
|
|
1
|
+
require "multi_json"
|
2
|
+
require "base64"
|
3
|
+
|
4
|
+
require "cfoundry/v2/base"
|
5
|
+
|
6
|
+
require "cfoundry/v2/app"
|
7
|
+
require "cfoundry/v2/framework"
|
8
|
+
require "cfoundry/v2/organization"
|
9
|
+
require "cfoundry/v2/runtime"
|
10
|
+
require "cfoundry/v2/service"
|
11
|
+
require "cfoundry/v2/service_binding"
|
12
|
+
require "cfoundry/v2/service_instance"
|
13
|
+
require "cfoundry/v2/service_plan"
|
14
|
+
require "cfoundry/v2/service_auth_token"
|
15
|
+
require "cfoundry/v2/space"
|
16
|
+
require "cfoundry/v2/user"
|
17
|
+
require "cfoundry/v2/domain"
|
18
|
+
require "cfoundry/v2/route"
|
19
|
+
|
20
|
+
module CFoundry::V2
|
21
|
+
# The primary API entrypoint. Wraps a BaseClient to provide nicer return
|
22
|
+
# values. Initialize with the target and, optionally, an auth token. These
|
23
|
+
# are the only two internal states.
|
24
|
+
class Client
|
25
|
+
# Internal BaseClient instance. Normally won't be touching this.
|
26
|
+
attr_reader :base
|
27
|
+
|
28
|
+
# [Organization] Currently targeted organization.
|
29
|
+
attr_accessor :current_organization
|
30
|
+
|
31
|
+
# [Space] Currently targeted space.
|
32
|
+
attr_accessor :current_space
|
33
|
+
|
34
|
+
|
35
|
+
# Create a new Client for interfacing with the given target.
|
36
|
+
#
|
37
|
+
# A token may also be provided to skip the login step.
|
38
|
+
def initialize(target = "http://api.cloudfoundry.com", token = nil)
|
39
|
+
@base = Base.new(target, token)
|
40
|
+
end
|
41
|
+
|
42
|
+
# The current target URL of the client.
|
43
|
+
def target
|
44
|
+
@base.target
|
45
|
+
end
|
46
|
+
|
47
|
+
# Current proxy user. Usually nil.
|
48
|
+
def proxy
|
49
|
+
@base.proxy
|
50
|
+
end
|
51
|
+
|
52
|
+
# Set the proxy user for the client. Must be authorized as an
|
53
|
+
# administrator for this to have any effect.
|
54
|
+
def proxy=(email)
|
55
|
+
@base.proxy = email
|
56
|
+
end
|
57
|
+
|
58
|
+
# Is the client tracing API requests?
|
59
|
+
def trace
|
60
|
+
@base.trace
|
61
|
+
end
|
62
|
+
|
63
|
+
# Set the tracing flag; if true, API requests and responses will be
|
64
|
+
# printed out.
|
65
|
+
def trace=(bool)
|
66
|
+
@base.trace = bool
|
67
|
+
end
|
68
|
+
|
69
|
+
# The currently authenticated user.
|
70
|
+
def current_user
|
71
|
+
if guid = @base.token_data[:user_id]
|
72
|
+
user = user(guid)
|
73
|
+
user.emails = [{ :value => @base.token_data[:email] }]
|
74
|
+
user
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Cloud metadata
|
79
|
+
def info
|
80
|
+
@base.info
|
81
|
+
end
|
82
|
+
|
83
|
+
# Login prompts
|
84
|
+
def login_prompts
|
85
|
+
if @base.uaa
|
86
|
+
@base.uaa.prompts
|
87
|
+
else
|
88
|
+
{ :username => ["text", "Email"],
|
89
|
+
:password => ["password", "Password"]
|
90
|
+
}
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Authenticate with the target. Sets the client token.
|
95
|
+
#
|
96
|
+
# Credentials is a hash, typically containing :username and :password
|
97
|
+
# keys.
|
98
|
+
#
|
99
|
+
# The values in the hash should mirror the prompts given by
|
100
|
+
# `login_prompts`.
|
101
|
+
def login(credentials)
|
102
|
+
@current_organization = nil
|
103
|
+
@current_space = nil
|
104
|
+
|
105
|
+
@base.token =
|
106
|
+
if @base.uaa
|
107
|
+
@base.uaa.authorize(credentials)
|
108
|
+
else
|
109
|
+
@base.create_token(
|
110
|
+
{ :password => credentials[:password] },
|
111
|
+
credentials[:username])[:token]
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Clear client token. No requests are made for this.
|
116
|
+
def logout
|
117
|
+
@base.token = nil
|
118
|
+
end
|
119
|
+
|
120
|
+
# Is an authentication token set on the client?
|
121
|
+
def logged_in?
|
122
|
+
!!@base.token
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
[ :app, :organization, :space, :user, :runtime, :framework, :service,
|
127
|
+
:domain, :route, :service_plan, :service_binding, :service_instance,
|
128
|
+
:service_auth_token
|
129
|
+
].each do |singular|
|
130
|
+
plural = :"#{singular}s"
|
131
|
+
|
132
|
+
classname = singular.to_s.capitalize.gsub(/(.)_(.)/) do
|
133
|
+
$1 + $2.upcase
|
134
|
+
end
|
135
|
+
|
136
|
+
klass = CFoundry::V2.const_get(classname)
|
137
|
+
|
138
|
+
has_space = klass.method_defined? :space
|
139
|
+
has_name = klass.method_defined? :name
|
140
|
+
|
141
|
+
define_method(singular) do |*args|
|
142
|
+
guid, _ = args
|
143
|
+
klass.new(guid, self)
|
144
|
+
end
|
145
|
+
|
146
|
+
define_method(plural) do |*args|
|
147
|
+
depth, query = args
|
148
|
+
depth ||= 1
|
149
|
+
|
150
|
+
if has_space && current_space
|
151
|
+
query ||= {}
|
152
|
+
query[:space_guid] ||= current_space.guid
|
153
|
+
end
|
154
|
+
|
155
|
+
@base.send(plural, depth, query)[:resources].collect do |json|
|
156
|
+
send(:"make_#{singular}", json)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
if has_name
|
161
|
+
define_method(:"#{singular}_by_name") do |name|
|
162
|
+
if has_space && current_space
|
163
|
+
current_space.send(plural, 1, :name => name).first
|
164
|
+
else
|
165
|
+
send(plural, 1, :name => name).first
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
define_method(:"#{singular}_from") do |path, *args|
|
171
|
+
send(
|
172
|
+
:"make_#{singular}",
|
173
|
+
@base.request_path(
|
174
|
+
:get,
|
175
|
+
path,
|
176
|
+
nil => :json,
|
177
|
+
:params => @base.params_from(args)))
|
178
|
+
end
|
179
|
+
|
180
|
+
define_method(:"#{plural}_from") do |path, *args|
|
181
|
+
@base.request_path(
|
182
|
+
:get,
|
183
|
+
path,
|
184
|
+
nil => :json,
|
185
|
+
:params => @base.params_from(args))[:resources].collect do |json|
|
186
|
+
send(:"make_#{singular}", json)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
define_method(:"make_#{singular}") do |json|
|
191
|
+
klass.new(
|
192
|
+
json[:metadata][:guid],
|
193
|
+
self,
|
194
|
+
json)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
@@ -0,0 +1,268 @@
|
|
1
|
+
require "multi_json"
|
2
|
+
|
3
|
+
module CFoundry::V2
|
4
|
+
class Model
|
5
|
+
class << self
|
6
|
+
def value_matches?(val, type)
|
7
|
+
case type
|
8
|
+
when Class
|
9
|
+
val.is_a?(type)
|
10
|
+
when Regexp
|
11
|
+
val.is_a?(String) && val =~ type
|
12
|
+
when :url
|
13
|
+
value_matches?(val, URI::regexp(%w(http https)))
|
14
|
+
when :https_url
|
15
|
+
value_matches?(val, URI::regexp("https"))
|
16
|
+
when :boolean
|
17
|
+
val.is_a?(TrueClass) || val.is_a?(FalseClass)
|
18
|
+
when Array
|
19
|
+
val.all? do |x|
|
20
|
+
value_matches?(x, type.first)
|
21
|
+
end
|
22
|
+
when Hash
|
23
|
+
val.is_a?(Hash) &&
|
24
|
+
type.all? { |name, subtype|
|
25
|
+
val.key?(name) && value_matches?(val[name], subtype)
|
26
|
+
}
|
27
|
+
else
|
28
|
+
val.is_a?(Object.const_get(type.to_s.capitalize))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def validate_type(val, type)
|
33
|
+
unless value_matches?(val, type)
|
34
|
+
raise "invalid attribute; expected #{type.inspect} but got #{val.inspect}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def defaults
|
39
|
+
@defaults ||= {}
|
40
|
+
end
|
41
|
+
|
42
|
+
def attribute(name, type, opts = {})
|
43
|
+
default = opts[:default] || nil
|
44
|
+
|
45
|
+
has_default = opts.key?(:default)
|
46
|
+
defaults[name] = default if has_default
|
47
|
+
|
48
|
+
define_method(name) {
|
49
|
+
manifest[:entity][name] || default
|
50
|
+
}
|
51
|
+
|
52
|
+
define_method(:"#{name}=") { |val|
|
53
|
+
unless has_default && val == default
|
54
|
+
Model.validate_type(val, type)
|
55
|
+
end
|
56
|
+
|
57
|
+
@manifest ||= {}
|
58
|
+
@manifest[:entity] ||= {}
|
59
|
+
@manifest[:entity][name] = val
|
60
|
+
@diff[name] = val
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_one(name, opts = {})
|
65
|
+
obj = opts[:as] || name
|
66
|
+
kls = obj.to_s.capitalize.gsub(/(.)_(.)/) do
|
67
|
+
$1 + $2.upcase
|
68
|
+
end
|
69
|
+
|
70
|
+
define_method(name) {
|
71
|
+
if @manifest && @manifest[:entity].key?(name)
|
72
|
+
@client.send(:"make_#{obj}", @manifest[:entity][name])
|
73
|
+
else
|
74
|
+
@client.send(
|
75
|
+
:"#{obj}_from",
|
76
|
+
send("#{name}_url"),
|
77
|
+
opts[:depth] || 1)
|
78
|
+
end
|
79
|
+
}
|
80
|
+
|
81
|
+
define_method(:"#{name}_url") {
|
82
|
+
manifest[:entity][:"#{name}_url"]
|
83
|
+
}
|
84
|
+
|
85
|
+
define_method(:"#{name}=") { |x|
|
86
|
+
Model.validate_type(x, CFoundry::V2.const_get(kls))
|
87
|
+
|
88
|
+
@manifest ||= {}
|
89
|
+
@manifest[:entity] ||= {}
|
90
|
+
@manifest[:entity][:"#{name}_guid"] =
|
91
|
+
@diff[:"#{name}_guid"] = x.guid
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
95
|
+
def to_many(plural, opts = {})
|
96
|
+
singular = plural.to_s.sub(/s$/, "").to_sym
|
97
|
+
|
98
|
+
object = opts[:as] || singular
|
99
|
+
plural_object = :"#{object}s"
|
100
|
+
|
101
|
+
kls = object.to_s.capitalize.gsub(/(.)_(.)/) do
|
102
|
+
$1 + $2.upcase
|
103
|
+
end
|
104
|
+
|
105
|
+
define_method(plural) { |*args|
|
106
|
+
depth, query = args
|
107
|
+
|
108
|
+
if @manifest && @manifest[:entity].key?(plural) && !depth
|
109
|
+
objs = @manifest[:entity][plural]
|
110
|
+
|
111
|
+
if query
|
112
|
+
find_by = query.keys.first
|
113
|
+
find_val = query.values.first
|
114
|
+
objs = objs.select { |o| o[:entity][find_by] == find_val }
|
115
|
+
end
|
116
|
+
|
117
|
+
objs.collect do |json|
|
118
|
+
@client.send(:"make_#{object}", json)
|
119
|
+
end
|
120
|
+
else
|
121
|
+
@client.send(
|
122
|
+
:"#{plural_object}_from",
|
123
|
+
"/v2/#{object_name}s/#@guid/#{plural}",
|
124
|
+
depth || opts[:depth],
|
125
|
+
query)
|
126
|
+
end
|
127
|
+
}
|
128
|
+
|
129
|
+
define_method(:"#{plural}_url") {
|
130
|
+
manifest[:entity][:"#{plural}_url"]
|
131
|
+
}
|
132
|
+
|
133
|
+
define_method(:"add_#{singular}") { |x|
|
134
|
+
# TODO: reflect this change in the app manifest?
|
135
|
+
Model.validate_type(x, CFoundry::V2.const_get(kls))
|
136
|
+
|
137
|
+
@client.base.request_path(
|
138
|
+
:put,
|
139
|
+
["v2", "#{object_name}s", @guid, plural, x.guid],
|
140
|
+
nil => :json)
|
141
|
+
}
|
142
|
+
|
143
|
+
define_method(:"remove_#{singular}") { |x|
|
144
|
+
# TODO: reflect this change in the app manifest?
|
145
|
+
Model.validate_type(x, CFoundry::V2.const_get(kls))
|
146
|
+
|
147
|
+
@client.base.request_path(
|
148
|
+
:delete,
|
149
|
+
["v2", "#{object_name}s", @guid, plural, x.guid],
|
150
|
+
nil => :json)
|
151
|
+
}
|
152
|
+
|
153
|
+
define_method(:"#{plural}=") { |xs|
|
154
|
+
Model.validate_type(x, [CFoundry::V2.const_get(kls)])
|
155
|
+
|
156
|
+
@manifest ||= {}
|
157
|
+
@manifest[:entity] ||= {}
|
158
|
+
@manifest[:entity][:"#{singular}_guids"] =
|
159
|
+
@diff[:"#{singular}_guids"] = xs.collect(&:guid)
|
160
|
+
}
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
attr_reader :guid
|
165
|
+
|
166
|
+
def initialize(guid, client, manifest = nil)
|
167
|
+
@guid = guid
|
168
|
+
@client = client
|
169
|
+
@manifest = manifest
|
170
|
+
@diff = {}
|
171
|
+
end
|
172
|
+
|
173
|
+
def manifest
|
174
|
+
@manifest ||= @client.base.send(object_name, @guid)
|
175
|
+
end
|
176
|
+
|
177
|
+
def inspect
|
178
|
+
"\#<#{self.class.name} '#@guid'>"
|
179
|
+
end
|
180
|
+
|
181
|
+
def object_name
|
182
|
+
@object_name ||=
|
183
|
+
self.class.name.split("::").last.gsub(
|
184
|
+
/([a-z])([A-Z])/,
|
185
|
+
'\1_\2').downcase
|
186
|
+
end
|
187
|
+
|
188
|
+
def invalidate!
|
189
|
+
@manifest = nil
|
190
|
+
@diff = {}
|
191
|
+
end
|
192
|
+
|
193
|
+
# this does a bit of extra processing to allow for
|
194
|
+
# `delete!' followed by `create!'
|
195
|
+
def create!
|
196
|
+
payload = {}
|
197
|
+
|
198
|
+
self.class.defaults.merge(@manifest[:entity]).each do |k, v|
|
199
|
+
if v.is_a?(Hash) && v.key?(:metadata)
|
200
|
+
# skip; there's a _guid attribute already
|
201
|
+
elsif v.is_a?(Array) && v.all? { |x|
|
202
|
+
x.is_a?(Hash) && x.key?(:metadata)
|
203
|
+
}
|
204
|
+
singular = k.to_s.sub(/s$/, "")
|
205
|
+
|
206
|
+
payload[:"#{singular}_guids"] = v.collect do |x|
|
207
|
+
if x.is_a?(Hash) && x.key?(:metadata)
|
208
|
+
x[:metadata][:guid]
|
209
|
+
else
|
210
|
+
x
|
211
|
+
end
|
212
|
+
end
|
213
|
+
elsif k.to_s.end_with?("_json") && v.is_a?(String)
|
214
|
+
payload[k] = MultiJson.load(v)
|
215
|
+
elsif k.to_s.end_with?("_url")
|
216
|
+
else
|
217
|
+
payload[k] = v
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
@manifest = @client.base.send(:"create_#{object_name}", payload)
|
222
|
+
|
223
|
+
@guid = @manifest[:metadata][:guid]
|
224
|
+
|
225
|
+
@diff.clear
|
226
|
+
|
227
|
+
true
|
228
|
+
end
|
229
|
+
|
230
|
+
def update!(diff = @diff)
|
231
|
+
@manifest = @client.base.send(:"update_#{object_name}", @guid, diff)
|
232
|
+
|
233
|
+
@diff.clear if diff == @diff
|
234
|
+
|
235
|
+
true
|
236
|
+
end
|
237
|
+
|
238
|
+
def delete!
|
239
|
+
@client.base.send(:"delete_#{object_name}", @guid)
|
240
|
+
|
241
|
+
@guid = nil
|
242
|
+
|
243
|
+
@diff.clear
|
244
|
+
|
245
|
+
if @manifest
|
246
|
+
@manifest.delete :metadata
|
247
|
+
end
|
248
|
+
|
249
|
+
true
|
250
|
+
end
|
251
|
+
|
252
|
+
def exists?
|
253
|
+
@client.base.send(object_name, @guid)
|
254
|
+
true
|
255
|
+
rescue CFoundry::APIError # TODO: NotFound would be better
|
256
|
+
false
|
257
|
+
end
|
258
|
+
|
259
|
+
def eql?(other)
|
260
|
+
other.is_a?(self.class) && @guid == other.guid
|
261
|
+
end
|
262
|
+
alias :== :eql?
|
263
|
+
|
264
|
+
def hash
|
265
|
+
@guid.hash
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|