nucleus 0.1.0 → 0.2.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.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -0
- data/CHANGELOG.md +18 -4
- data/README.md +28 -40
- data/Rakefile +137 -137
- data/config/nucleus_config.rb +0 -4
- data/lib/nucleus/adapter_resolver.rb +115 -115
- data/lib/nucleus/adapters/buildpack_translator.rb +79 -79
- data/lib/nucleus/adapters/v1/cloud_control/application.rb +108 -108
- data/lib/nucleus/adapters/v1/cloud_control/authentication.rb +27 -27
- data/lib/nucleus/adapters/v1/cloud_control/cloud_control.rb +153 -153
- data/lib/nucleus/adapters/v1/cloud_control/domains.rb +68 -68
- data/lib/nucleus/adapters/v1/cloud_control/logs.rb +103 -103
- data/lib/nucleus/adapters/v1/cloud_control/vars.rb +88 -88
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/domains.rb +149 -149
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/logs.rb +303 -303
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/services.rb +286 -286
- data/lib/nucleus/adapters/v1/heroku/heroku.rb +2 -2
- data/lib/nucleus/adapters/v1/heroku/logs.rb +108 -108
- data/lib/nucleus/core/adapter_authentication_inductor.rb +0 -2
- data/lib/nucleus/core/adapter_extensions/auth/http_basic_auth_client.rb +37 -37
- data/lib/nucleus/core/adapter_extensions/http_client.rb +177 -177
- data/lib/nucleus/core/common/files/archive_extractor.rb +112 -112
- data/lib/nucleus/core/common/files/archiver.rb +91 -91
- data/lib/nucleus/core/common/logging/request_log_formatter.rb +48 -48
- data/lib/nucleus/core/error_messages.rb +127 -127
- data/lib/nucleus/core/models/abstract_model.rb +29 -29
- data/lib/nucleus/scripts/load_dependencies.rb +0 -1
- data/lib/nucleus/scripts/setup_config.rb +28 -28
- data/lib/nucleus/version.rb +3 -3
- data/nucleus.gemspec +10 -12
- data/spec/factories/models.rb +63 -61
- data/spec/integration/api/auth_spec.rb +58 -58
- data/spec/test_suites.rake +31 -31
- data/spec/unit/common/helpers/auth_helper_spec.rb +73 -73
- data/spec/unit/common/oauth2_auth_client_spec.rb +1 -1
- data/tasks/compatibility.rake +113 -113
- data/tasks/evaluation.rake +162 -162
- metadata +16 -30
@@ -1,286 +1,286 @@
|
|
1
|
-
module Nucleus
|
2
|
-
module Adapters
|
3
|
-
module V1
|
4
|
-
class CloudFoundryV2 < Stub
|
5
|
-
# Cloud Foundry, operations for the application's addons
|
6
|
-
module Services
|
7
|
-
# @see Stub#services
|
8
|
-
def services
|
9
|
-
get('/v2/services?inline-relations-depth=1').body[:resources].collect do |service|
|
10
|
-
# show only services that are both, active and bindable
|
11
|
-
next unless service[:entity][:active] && service[:entity][:bindable]
|
12
|
-
to_nucleus_service(service)
|
13
|
-
end.compact
|
14
|
-
end
|
15
|
-
|
16
|
-
# @see Stub#service
|
17
|
-
def service(service_id_or_name)
|
18
|
-
service_guid = service_guid(service_id_or_name)
|
19
|
-
to_nucleus_service(get("/v2/services/#{service_guid}?inline-relations-depth=1").body)
|
20
|
-
end
|
21
|
-
|
22
|
-
# @see Stub#service_plans
|
23
|
-
def service_plans(service_id_or_name)
|
24
|
-
service_guid = service_guid(service_id_or_name)
|
25
|
-
load_plans(service_guid).collect { |plan| to_nucleus_plan(plan) }
|
26
|
-
end
|
27
|
-
|
28
|
-
# @see Stub#service_plan
|
29
|
-
def service_plan(service_id_or_name, plan_id)
|
30
|
-
service_guid = service_guid(service_id_or_name)
|
31
|
-
plan_guid = plan_guid(service_guid, plan_id, Errors::AdapterResourceNotFoundError)
|
32
|
-
to_nucleus_plan(get("/v2/service_plans/#{plan_guid}").body)
|
33
|
-
end
|
34
|
-
|
35
|
-
# @see Stub#installed_services
|
36
|
-
def installed_services(application_name_or_id)
|
37
|
-
app_guid = app_guid(application_name_or_id)
|
38
|
-
get("/v2/apps/#{app_guid}/service_bindings?inline-relations-depth=1").body[:resources].collect do |binding|
|
39
|
-
to_nucleus_installed_service(binding)
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
# @see Stub#installed_service
|
44
|
-
def installed_service(application_name_or_id, service_id_or_name)
|
45
|
-
app_guid = app_guid(application_name_or_id)
|
46
|
-
service_guid = service_guid(service_id_or_name)
|
47
|
-
cf_binding = binding(app_guid, service_guid)
|
48
|
-
# make sure there is a binding
|
49
|
-
fail Errors::AdapterResourceNotFoundError,
|
50
|
-
"No such service '#{service_id_or_name}' for application '#{application_name_or_id}'" unless cf_binding
|
51
|
-
to_nucleus_installed_service(cf_binding)
|
52
|
-
end
|
53
|
-
|
54
|
-
# @see Stub#add_service
|
55
|
-
def add_service(application_name_or_id, service_entity, plan_entity)
|
56
|
-
app_guid = app_guid(application_name_or_id)
|
57
|
-
service_guid = service_guid(service_entity[:id], Errors::SemanticAdapterRequestError)
|
58
|
-
cf_service = load_allowed_service(service_entity, service_guid)
|
59
|
-
|
60
|
-
# get the plan, throws 422 if the plan could not be found
|
61
|
-
plan_guid = plan_guid(service_guid, plan_entity[:id])
|
62
|
-
|
63
|
-
# create new service instance
|
64
|
-
instance_request_body = { space_guid: user_space_guid, service_plan_guid: plan_guid,
|
65
|
-
name: "#{cf_service[:entity][:label]}-#{application_name_or_id}-nucleus" }
|
66
|
-
cf_instance = post('/v2/service_instances', body: instance_request_body).body
|
67
|
-
|
68
|
-
# bind the created service instance to the application
|
69
|
-
binding_request_body = { service_instance_guid: cf_instance[:metadata][:guid], app_guid: app_guid }
|
70
|
-
cf_binding = post('/v2/service_bindings', body: binding_request_body).body
|
71
|
-
|
72
|
-
# created service presentation
|
73
|
-
to_nucleus_installed_service(cf_binding, cf_service, cf_instance)
|
74
|
-
end
|
75
|
-
|
76
|
-
# @see Stub#change_service
|
77
|
-
def change_service(application_name_or_id, service_id, plan_entity)
|
78
|
-
app_guid = app_guid(application_name_or_id)
|
79
|
-
service_guid = service_guid(service_id)
|
80
|
-
cf_service = get("/v2/services/#{service_guid}").body
|
81
|
-
fail_with(:service_not_updateable, [service_id]) unless cf_service[:entity][:plan_updateable]
|
82
|
-
|
83
|
-
cf_binding = binding(app_guid, service_guid)
|
84
|
-
|
85
|
-
# get the plan, throws 422 if the plan could not be found
|
86
|
-
plan_guid = plan_guid(service_guid, plan_entity[:id])
|
87
|
-
cf_instance = put("/v2/service_instances/#{cf_binding[:entity][:service_instance_guid]}",
|
88
|
-
body: { service_plan_guid: plan_guid }).body
|
89
|
-
to_nucleus_installed_service(cf_binding, cf_service, cf_instance)
|
90
|
-
end
|
91
|
-
|
92
|
-
# @see Stub#remove_service
|
93
|
-
def remove_service(application_name_or_id, service_id)
|
94
|
-
app_guid = app_guid(application_name_or_id)
|
95
|
-
service_guid = service_guid(service_id)
|
96
|
-
# sadly we can't resolve the binding and instance from the service_id with ease
|
97
|
-
# we therefore setup a chain to resolve the binding and instance from the active pla
|
98
|
-
binding = binding(app_guid, service_guid)
|
99
|
-
|
100
|
-
# now remove the binding from the application
|
101
|
-
delete("/v2/apps/#{app_guid}/service_bindings/#{binding[:metadata][:guid]}", expects: [201])
|
102
|
-
# and finally delete the service instance
|
103
|
-
delete("/v2/service_instances/#{binding[:entity][:service_instance_guid]}")
|
104
|
-
end
|
105
|
-
|
106
|
-
private
|
107
|
-
|
108
|
-
def load_allowed_service(service_entity, service_guid)
|
109
|
-
begin
|
110
|
-
cf_service = get("/v2/services/#{service_guid}").body
|
111
|
-
rescue Errors::AdapterResourceNotFoundError
|
112
|
-
# convert to semantic error with the service being a body, not a path entity
|
113
|
-
raise Errors::SemanticAdapterRequestError,
|
114
|
-
"Invalid service: Could not find service with the ID '#{service_entity[:id]}'"
|
115
|
-
end
|
116
|
-
|
117
|
-
# must be active and bindable?
|
118
|
-
# currently we focus only on bindable services
|
119
|
-
fail_with(:service_not_bindable, [service_entity[:id]]) unless cf_service[:entity][:bindable]
|
120
|
-
# service must be active, otherwise we can't create the instance
|
121
|
-
fail_with(:service_not_active, [service_entity[:id]]) unless cf_service[:entity][:active]
|
122
|
-
# service seems to be valid, return
|
123
|
-
cf_service
|
124
|
-
end
|
125
|
-
|
126
|
-
def remove_all_services(app_guid)
|
127
|
-
get("/v2/apps/#{app_guid}/service_bindings").body[:resources].collect do |binding|
|
128
|
-
# remove the binding from the application
|
129
|
-
delete("/v2/apps/#{app_guid}/service_bindings/#{binding[:metadata][:guid]}", expects: [201])
|
130
|
-
# and delete the service instance to prevent orphans
|
131
|
-
delete("/v2/service_instances/#{binding[:entity][:service_instance_guid]}")
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
def service_guid(service_id_or_name, error_class = Errors::AdapterResourceNotFoundError)
|
136
|
-
return service_id_or_name if guid?(service_id_or_name)
|
137
|
-
# list all available services
|
138
|
-
services = get('/v2/services').body[:resources]
|
139
|
-
# find a match and use the service's guid
|
140
|
-
service_match = services.find { |service| service[:entity][:label] == service_id_or_name }
|
141
|
-
fail error_class,
|
142
|
-
"Invalid service: Could not find service with name '#{service_id_or_name}'" unless service_match
|
143
|
-
service_match[:metadata][:guid]
|
144
|
-
end
|
145
|
-
|
146
|
-
def binding(app_guid, service_id)
|
147
|
-
service_plans = get("/v2/services/#{service_id}/service_plans").body[:resources]
|
148
|
-
service_plan_ids = service_plans.collect { |plan| plan[:metadata][:guid] }
|
149
|
-
app_bindings = get("/v2/apps/#{app_guid}/service_bindings?inline-relations-depth=1").body[:resources]
|
150
|
-
# the plan must be bound to the app via an instance
|
151
|
-
app_bindings.find do |binding|
|
152
|
-
service_plan_ids.include?(binding[:entity][:service_instance][:entity][:service_plan_guid])
|
153
|
-
end
|
154
|
-
end
|
155
|
-
|
156
|
-
def plan_guid(service_id, plan_name_or_id, error_class = Errors::SemanticAdapterRequestError)
|
157
|
-
return plan_name_or_id if guid?(plan_name_or_id)
|
158
|
-
# list all plans for the service
|
159
|
-
plans = get("/v2/services/#{service_id}/service_plans").body[:resources]
|
160
|
-
# find a match and use the plan's guid
|
161
|
-
plan_match = plans.find { |plan| plan[:entity][:name] == plan_name_or_id }
|
162
|
-
fail error_class,
|
163
|
-
"Invalid plan: No such plan '#{plan_name_or_id}' for service '#{service_id}'" unless plan_match
|
164
|
-
plan_match[:metadata][:guid]
|
165
|
-
end
|
166
|
-
|
167
|
-
def load_plans(service_id)
|
168
|
-
get("/v2/services/#{service_id}/service_plans").body[:resources]
|
169
|
-
end
|
170
|
-
|
171
|
-
# Memoize this detection.
|
172
|
-
# The information is not critical, but takes some time to evaluate.
|
173
|
-
# Values are not expected to change often.
|
174
|
-
def free_plan?(service_id, plans = nil)
|
175
|
-
@free_plans ||= {}
|
176
|
-
return @free_plans[service_id] if @free_plans.key?(service_id)
|
177
|
-
plans = load_plans(service_id) unless plans
|
178
|
-
@free_plans[service_id] = plans.any? { |plan| plan[:entity][:free] }
|
179
|
-
@free_plans[service_id]
|
180
|
-
end
|
181
|
-
|
182
|
-
def to_nucleus_plan(cf_plan)
|
183
|
-
plan = cf_plan[:entity]
|
184
|
-
plan[:id] = cf_plan[:metadata][:guid]
|
185
|
-
plan[:created_at] = cf_plan[:metadata][:created_at]
|
186
|
-
plan[:updated_at] = cf_plan[:metadata][:updated_at]
|
187
|
-
plan[:costs] = []
|
188
|
-
plan
|
189
|
-
|
190
|
-
# TODO: determine prices for CF services
|
191
|
-
# we know how IBM handles the costs, but can't determine the country
|
192
|
-
# we know how Pivotal IO handles the costs
|
193
|
-
# but what do the others???
|
194
|
-
|
195
|
-
# extra = Oj.load(plan[:extra])
|
196
|
-
# if plan[:free]
|
197
|
-
# plan[:costs] = { period: '', per_instance: false, price: { amount: 0.00, currency: nil} }
|
198
|
-
# elsif endpoint_url.include?('pivotal.io')
|
199
|
-
# # show prices for Pivotal Web Services
|
200
|
-
# # see for an explanation: http://docs.pivotal.io/pivotalcf/services/catalog-metadata.html
|
201
|
-
# plan[:costs] = extra[:costs].collect do |cost|
|
202
|
-
# prices = cost[:amount].collect { |currency, amount| { currency: currency, amount: amount } }
|
203
|
-
# { per_instance: false, period: cost[:unit], price: prices }
|
204
|
-
# end
|
205
|
-
# elsif endpoint_url.include?('bluemix.net')
|
206
|
-
# # show prices for IBM Bluemix
|
207
|
-
# else
|
208
|
-
# # fallback, unknown CF system
|
209
|
-
# end
|
210
|
-
end
|
211
|
-
|
212
|
-
def to_nucleus_service(cf_service)
|
213
|
-
service = cf_service[:entity]
|
214
|
-
service = apply_metadata(service, cf_service)
|
215
|
-
service[:name] = service.delete(:label)
|
216
|
-
service[:release] = service.delete(:version)
|
217
|
-
if cf_service[:entity].key?(:service_plans)
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
# CF does not have service dependencies
|
224
|
-
service[:required_services] = service.delete(:requires)
|
225
|
-
# description and documentation_url should already be set
|
226
|
-
service
|
227
|
-
end
|
228
|
-
|
229
|
-
# Show the installed service. Therefore we need:<br>
|
230
|
-
# <ul>
|
231
|
-
# <li>the classic service for the basic information (version, name, ...)</li>
|
232
|
-
# <li>the binding for the properties and metadata (id, timestamps, ...)</li>
|
233
|
-
# <li>the bound instance for the active plan and web_url</li>
|
234
|
-
# </ul>
|
235
|
-
def to_nucleus_installed_service(cf_binding, cf_service = nil, cf_instance = nil)
|
236
|
-
# load if not provided
|
237
|
-
cf_instance = load_instance(cf_binding) unless cf_instance
|
238
|
-
cf_service = load_service(cf_instance) unless cf_service
|
239
|
-
# load if not provided
|
240
|
-
unless cf_service
|
241
|
-
cf_service = get("/v2/service_plans/#{cf_instance[:entity][:service_plan_guid]}"\
|
242
|
-
'?inline-relations-depth=1').body[:entity][:service]
|
243
|
-
end
|
244
|
-
|
245
|
-
# active_plan, web_url, properties
|
246
|
-
service = to_nucleus_service(cf_service)
|
247
|
-
# use the metadata of the binding, is more future proof than instance metadata
|
248
|
-
apply_metadata(service, cf_binding)
|
249
|
-
service[:active_plan] = cf_instance[:entity][:service_plan_guid]
|
250
|
-
service[:web_url] = cf_instance[:entity][:dashboard_url]
|
251
|
-
service[:properties] = binding_properties(cf_binding)
|
252
|
-
service
|
253
|
-
end
|
254
|
-
|
255
|
-
def load_instance(cf_binding)
|
256
|
-
if cf_binding[:entity].key?(:service_instance)
|
257
|
-
# use if nested property is available
|
258
|
-
cf_binding[:entity][:service_instance]
|
259
|
-
else
|
260
|
-
get("/v2/service_instances/#{cf_binding[:entity][:service_instance_guid]}").body
|
261
|
-
end
|
262
|
-
end
|
263
|
-
|
264
|
-
def load_service(cf_instance)
|
265
|
-
get("/v2/service_plans/#{cf_instance[:entity][:service_plan_guid]}"\
|
266
|
-
'?inline-relations-depth=1').body[:entity][:service]
|
267
|
-
end
|
268
|
-
|
269
|
-
def binding_properties(binding)
|
270
|
-
# in the credentials there are information such as: hostname, username, password, license keys, ...
|
271
|
-
binding[:entity][:credentials].collect do |key, value|
|
272
|
-
{ key: key, value: value, description: nil }
|
273
|
-
end
|
274
|
-
end
|
275
|
-
|
276
|
-
def apply_metadata(apply_to, cf_object)
|
277
|
-
apply_to[:id] = cf_object[:metadata][:guid]
|
278
|
-
apply_to[:created_at] = cf_object[:metadata][:created_at]
|
279
|
-
apply_to[:updated_at] = cf_object[:metadata][:updated_at]
|
280
|
-
apply_to
|
281
|
-
end
|
282
|
-
end
|
283
|
-
end
|
284
|
-
end
|
285
|
-
end
|
286
|
-
end
|
1
|
+
module Nucleus
|
2
|
+
module Adapters
|
3
|
+
module V1
|
4
|
+
class CloudFoundryV2 < Stub
|
5
|
+
# Cloud Foundry, operations for the application's addons
|
6
|
+
module Services
|
7
|
+
# @see Stub#services
|
8
|
+
def services
|
9
|
+
get('/v2/services?inline-relations-depth=1').body[:resources].collect do |service|
|
10
|
+
# show only services that are both, active and bindable
|
11
|
+
next unless service[:entity][:active] && service[:entity][:bindable]
|
12
|
+
to_nucleus_service(service)
|
13
|
+
end.compact
|
14
|
+
end
|
15
|
+
|
16
|
+
# @see Stub#service
|
17
|
+
def service(service_id_or_name)
|
18
|
+
service_guid = service_guid(service_id_or_name)
|
19
|
+
to_nucleus_service(get("/v2/services/#{service_guid}?inline-relations-depth=1").body)
|
20
|
+
end
|
21
|
+
|
22
|
+
# @see Stub#service_plans
|
23
|
+
def service_plans(service_id_or_name)
|
24
|
+
service_guid = service_guid(service_id_or_name)
|
25
|
+
load_plans(service_guid).collect { |plan| to_nucleus_plan(plan) }
|
26
|
+
end
|
27
|
+
|
28
|
+
# @see Stub#service_plan
|
29
|
+
def service_plan(service_id_or_name, plan_id)
|
30
|
+
service_guid = service_guid(service_id_or_name)
|
31
|
+
plan_guid = plan_guid(service_guid, plan_id, Errors::AdapterResourceNotFoundError)
|
32
|
+
to_nucleus_plan(get("/v2/service_plans/#{plan_guid}").body)
|
33
|
+
end
|
34
|
+
|
35
|
+
# @see Stub#installed_services
|
36
|
+
def installed_services(application_name_or_id)
|
37
|
+
app_guid = app_guid(application_name_or_id)
|
38
|
+
get("/v2/apps/#{app_guid}/service_bindings?inline-relations-depth=1").body[:resources].collect do |binding|
|
39
|
+
to_nucleus_installed_service(binding)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# @see Stub#installed_service
|
44
|
+
def installed_service(application_name_or_id, service_id_or_name)
|
45
|
+
app_guid = app_guid(application_name_or_id)
|
46
|
+
service_guid = service_guid(service_id_or_name)
|
47
|
+
cf_binding = binding(app_guid, service_guid)
|
48
|
+
# make sure there is a binding
|
49
|
+
fail Errors::AdapterResourceNotFoundError,
|
50
|
+
"No such service '#{service_id_or_name}' for application '#{application_name_or_id}'" unless cf_binding
|
51
|
+
to_nucleus_installed_service(cf_binding)
|
52
|
+
end
|
53
|
+
|
54
|
+
# @see Stub#add_service
|
55
|
+
def add_service(application_name_or_id, service_entity, plan_entity)
|
56
|
+
app_guid = app_guid(application_name_or_id)
|
57
|
+
service_guid = service_guid(service_entity[:id], Errors::SemanticAdapterRequestError)
|
58
|
+
cf_service = load_allowed_service(service_entity, service_guid)
|
59
|
+
|
60
|
+
# get the plan, throws 422 if the plan could not be found
|
61
|
+
plan_guid = plan_guid(service_guid, plan_entity[:id])
|
62
|
+
|
63
|
+
# create new service instance
|
64
|
+
instance_request_body = { space_guid: user_space_guid, service_plan_guid: plan_guid,
|
65
|
+
name: "#{cf_service[:entity][:label]}-#{application_name_or_id}-nucleus" }
|
66
|
+
cf_instance = post('/v2/service_instances', body: instance_request_body).body
|
67
|
+
|
68
|
+
# bind the created service instance to the application
|
69
|
+
binding_request_body = { service_instance_guid: cf_instance[:metadata][:guid], app_guid: app_guid }
|
70
|
+
cf_binding = post('/v2/service_bindings', body: binding_request_body).body
|
71
|
+
|
72
|
+
# created service presentation
|
73
|
+
to_nucleus_installed_service(cf_binding, cf_service, cf_instance)
|
74
|
+
end
|
75
|
+
|
76
|
+
# @see Stub#change_service
|
77
|
+
def change_service(application_name_or_id, service_id, plan_entity)
|
78
|
+
app_guid = app_guid(application_name_or_id)
|
79
|
+
service_guid = service_guid(service_id)
|
80
|
+
cf_service = get("/v2/services/#{service_guid}").body
|
81
|
+
fail_with(:service_not_updateable, [service_id]) unless cf_service[:entity][:plan_updateable]
|
82
|
+
|
83
|
+
cf_binding = binding(app_guid, service_guid)
|
84
|
+
|
85
|
+
# get the plan, throws 422 if the plan could not be found
|
86
|
+
plan_guid = plan_guid(service_guid, plan_entity[:id])
|
87
|
+
cf_instance = put("/v2/service_instances/#{cf_binding[:entity][:service_instance_guid]}",
|
88
|
+
body: { service_plan_guid: plan_guid }).body
|
89
|
+
to_nucleus_installed_service(cf_binding, cf_service, cf_instance)
|
90
|
+
end
|
91
|
+
|
92
|
+
# @see Stub#remove_service
|
93
|
+
def remove_service(application_name_or_id, service_id)
|
94
|
+
app_guid = app_guid(application_name_or_id)
|
95
|
+
service_guid = service_guid(service_id)
|
96
|
+
# sadly we can't resolve the binding and instance from the service_id with ease
|
97
|
+
# we therefore setup a chain to resolve the binding and instance from the active pla
|
98
|
+
binding = binding(app_guid, service_guid)
|
99
|
+
|
100
|
+
# now remove the binding from the application
|
101
|
+
delete("/v2/apps/#{app_guid}/service_bindings/#{binding[:metadata][:guid]}", expects: [201])
|
102
|
+
# and finally delete the service instance
|
103
|
+
delete("/v2/service_instances/#{binding[:entity][:service_instance_guid]}")
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def load_allowed_service(service_entity, service_guid)
|
109
|
+
begin
|
110
|
+
cf_service = get("/v2/services/#{service_guid}").body
|
111
|
+
rescue Errors::AdapterResourceNotFoundError
|
112
|
+
# convert to semantic error with the service being a body, not a path entity
|
113
|
+
raise Errors::SemanticAdapterRequestError,
|
114
|
+
"Invalid service: Could not find service with the ID '#{service_entity[:id]}'"
|
115
|
+
end
|
116
|
+
|
117
|
+
# must be active and bindable?
|
118
|
+
# currently we focus only on bindable services
|
119
|
+
fail_with(:service_not_bindable, [service_entity[:id]]) unless cf_service[:entity][:bindable]
|
120
|
+
# service must be active, otherwise we can't create the instance
|
121
|
+
fail_with(:service_not_active, [service_entity[:id]]) unless cf_service[:entity][:active]
|
122
|
+
# service seems to be valid, return
|
123
|
+
cf_service
|
124
|
+
end
|
125
|
+
|
126
|
+
def remove_all_services(app_guid)
|
127
|
+
get("/v2/apps/#{app_guid}/service_bindings").body[:resources].collect do |binding|
|
128
|
+
# remove the binding from the application
|
129
|
+
delete("/v2/apps/#{app_guid}/service_bindings/#{binding[:metadata][:guid]}", expects: [201])
|
130
|
+
# and delete the service instance to prevent orphans
|
131
|
+
delete("/v2/service_instances/#{binding[:entity][:service_instance_guid]}")
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def service_guid(service_id_or_name, error_class = Errors::AdapterResourceNotFoundError)
|
136
|
+
return service_id_or_name if guid?(service_id_or_name)
|
137
|
+
# list all available services
|
138
|
+
services = get('/v2/services').body[:resources]
|
139
|
+
# find a match and use the service's guid
|
140
|
+
service_match = services.find { |service| service[:entity][:label] == service_id_or_name }
|
141
|
+
fail error_class,
|
142
|
+
"Invalid service: Could not find service with name '#{service_id_or_name}'" unless service_match
|
143
|
+
service_match[:metadata][:guid]
|
144
|
+
end
|
145
|
+
|
146
|
+
def binding(app_guid, service_id)
|
147
|
+
service_plans = get("/v2/services/#{service_id}/service_plans").body[:resources]
|
148
|
+
service_plan_ids = service_plans.collect { |plan| plan[:metadata][:guid] }
|
149
|
+
app_bindings = get("/v2/apps/#{app_guid}/service_bindings?inline-relations-depth=1").body[:resources]
|
150
|
+
# the plan must be bound to the app via an instance
|
151
|
+
app_bindings.find do |binding|
|
152
|
+
service_plan_ids.include?(binding[:entity][:service_instance][:entity][:service_plan_guid])
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def plan_guid(service_id, plan_name_or_id, error_class = Errors::SemanticAdapterRequestError)
|
157
|
+
return plan_name_or_id if guid?(plan_name_or_id)
|
158
|
+
# list all plans for the service
|
159
|
+
plans = get("/v2/services/#{service_id}/service_plans").body[:resources]
|
160
|
+
# find a match and use the plan's guid
|
161
|
+
plan_match = plans.find { |plan| plan[:entity][:name] == plan_name_or_id }
|
162
|
+
fail error_class,
|
163
|
+
"Invalid plan: No such plan '#{plan_name_or_id}' for service '#{service_id}'" unless plan_match
|
164
|
+
plan_match[:metadata][:guid]
|
165
|
+
end
|
166
|
+
|
167
|
+
def load_plans(service_id)
|
168
|
+
get("/v2/services/#{service_id}/service_plans").body[:resources]
|
169
|
+
end
|
170
|
+
|
171
|
+
# Memoize this detection.
|
172
|
+
# The information is not critical, but takes some time to evaluate.
|
173
|
+
# Values are not expected to change often.
|
174
|
+
def free_plan?(service_id, plans = nil)
|
175
|
+
@free_plans ||= {}
|
176
|
+
return @free_plans[service_id] if @free_plans.key?(service_id)
|
177
|
+
plans = load_plans(service_id) unless plans
|
178
|
+
@free_plans[service_id] = plans.any? { |plan| plan[:entity][:free] }
|
179
|
+
@free_plans[service_id]
|
180
|
+
end
|
181
|
+
|
182
|
+
def to_nucleus_plan(cf_plan)
|
183
|
+
plan = cf_plan[:entity]
|
184
|
+
plan[:id] = cf_plan[:metadata][:guid]
|
185
|
+
plan[:created_at] = cf_plan[:metadata][:created_at]
|
186
|
+
plan[:updated_at] = cf_plan[:metadata][:updated_at]
|
187
|
+
plan[:costs] = []
|
188
|
+
plan
|
189
|
+
|
190
|
+
# TODO: determine prices for CF services
|
191
|
+
# we know how IBM handles the costs, but can't determine the country
|
192
|
+
# we know how Pivotal IO handles the costs
|
193
|
+
# but what do the others???
|
194
|
+
|
195
|
+
# extra = Oj.load(plan[:extra])
|
196
|
+
# if plan[:free]
|
197
|
+
# plan[:costs] = { period: '', per_instance: false, price: { amount: 0.00, currency: nil} }
|
198
|
+
# elsif endpoint_url.include?('pivotal.io')
|
199
|
+
# # show prices for Pivotal Web Services
|
200
|
+
# # see for an explanation: http://docs.pivotal.io/pivotalcf/services/catalog-metadata.html
|
201
|
+
# plan[:costs] = extra[:costs].collect do |cost|
|
202
|
+
# prices = cost[:amount].collect { |currency, amount| { currency: currency, amount: amount } }
|
203
|
+
# { per_instance: false, period: cost[:unit], price: prices }
|
204
|
+
# end
|
205
|
+
# elsif endpoint_url.include?('bluemix.net')
|
206
|
+
# # show prices for IBM Bluemix
|
207
|
+
# else
|
208
|
+
# # fallback, unknown CF system
|
209
|
+
# end
|
210
|
+
end
|
211
|
+
|
212
|
+
def to_nucleus_service(cf_service)
|
213
|
+
service = cf_service[:entity]
|
214
|
+
service = apply_metadata(service, cf_service)
|
215
|
+
service[:name] = service.delete(:label)
|
216
|
+
service[:release] = service.delete(:version)
|
217
|
+
service[:free_plan] = if cf_service[:entity].key?(:service_plans)
|
218
|
+
# use preloaded plans if available
|
219
|
+
free_plan?(service[:id], cf_service[:entity][:service_plans])
|
220
|
+
else
|
221
|
+
free_plan?(service[:id])
|
222
|
+
end
|
223
|
+
# CF does not have service dependencies
|
224
|
+
service[:required_services] = service.delete(:requires)
|
225
|
+
# description and documentation_url should already be set
|
226
|
+
service
|
227
|
+
end
|
228
|
+
|
229
|
+
# Show the installed service. Therefore we need:<br>
|
230
|
+
# <ul>
|
231
|
+
# <li>the classic service for the basic information (version, name, ...)</li>
|
232
|
+
# <li>the binding for the properties and metadata (id, timestamps, ...)</li>
|
233
|
+
# <li>the bound instance for the active plan and web_url</li>
|
234
|
+
# </ul>
|
235
|
+
def to_nucleus_installed_service(cf_binding, cf_service = nil, cf_instance = nil)
|
236
|
+
# load if not provided
|
237
|
+
cf_instance = load_instance(cf_binding) unless cf_instance
|
238
|
+
cf_service = load_service(cf_instance) unless cf_service
|
239
|
+
# load if not provided
|
240
|
+
unless cf_service
|
241
|
+
cf_service = get("/v2/service_plans/#{cf_instance[:entity][:service_plan_guid]}"\
|
242
|
+
'?inline-relations-depth=1').body[:entity][:service]
|
243
|
+
end
|
244
|
+
|
245
|
+
# active_plan, web_url, properties
|
246
|
+
service = to_nucleus_service(cf_service)
|
247
|
+
# use the metadata of the binding, is more future proof than instance metadata
|
248
|
+
apply_metadata(service, cf_binding)
|
249
|
+
service[:active_plan] = cf_instance[:entity][:service_plan_guid]
|
250
|
+
service[:web_url] = cf_instance[:entity][:dashboard_url]
|
251
|
+
service[:properties] = binding_properties(cf_binding)
|
252
|
+
service
|
253
|
+
end
|
254
|
+
|
255
|
+
def load_instance(cf_binding)
|
256
|
+
if cf_binding[:entity].key?(:service_instance)
|
257
|
+
# use if nested property is available
|
258
|
+
cf_binding[:entity][:service_instance]
|
259
|
+
else
|
260
|
+
get("/v2/service_instances/#{cf_binding[:entity][:service_instance_guid]}").body
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
def load_service(cf_instance)
|
265
|
+
get("/v2/service_plans/#{cf_instance[:entity][:service_plan_guid]}"\
|
266
|
+
'?inline-relations-depth=1').body[:entity][:service]
|
267
|
+
end
|
268
|
+
|
269
|
+
def binding_properties(binding)
|
270
|
+
# in the credentials there are information such as: hostname, username, password, license keys, ...
|
271
|
+
binding[:entity][:credentials].collect do |key, value|
|
272
|
+
{ key: key, value: value, description: nil }
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
def apply_metadata(apply_to, cf_object)
|
277
|
+
apply_to[:id] = cf_object[:metadata][:guid]
|
278
|
+
apply_to[:created_at] = cf_object[:metadata][:created_at]
|
279
|
+
apply_to[:updated_at] = cf_object[:metadata][:updated_at]
|
280
|
+
apply_to
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|