module Nucleus::Adapters::V1::CloudFoundryV2::Services

Cloud Foundry, operations for the application’s addons

Public Instance Methods

add_service(application_name_or_id, service_entity, plan_entity) click to toggle source

@see Stub#add_service

# File lib/nucleus/adapters/v1/cloud_foundry_v2/services.rb, line 55
def add_service(application_name_or_id, service_entity, plan_entity)
  app_guid = app_guid(application_name_or_id)
  service_guid = service_guid(service_entity[:id], Errors::SemanticAdapterRequestError)
  cf_service = load_allowed_service(service_entity, service_guid)

  # get the plan, throws 422 if the plan could not be found
  plan_guid = plan_guid(service_guid, plan_entity[:id])

  # create new service instance
  instance_request_body = { space_guid: user_space_guid, service_plan_guid: plan_guid,
                            name: "#{cf_service[:entity][:label]}-#{application_name_or_id}-nucleus" }
  cf_instance = post('/v2/service_instances', body: instance_request_body).body

  # bind the created service instance to the application
  binding_request_body = { service_instance_guid: cf_instance[:metadata][:guid], app_guid: app_guid }
  cf_binding = post('/v2/service_bindings', body: binding_request_body).body

  # created service presentation
  to_nucleus_installed_service(cf_binding, cf_service, cf_instance)
end
change_service(application_name_or_id, service_id, plan_entity) click to toggle source

@see Stub#change_service

# File lib/nucleus/adapters/v1/cloud_foundry_v2/services.rb, line 77
def change_service(application_name_or_id, service_id, plan_entity)
  app_guid = app_guid(application_name_or_id)
  service_guid = service_guid(service_id)
  cf_service = get("/v2/services/#{service_guid}").body
  fail_with(:service_not_updateable, [service_id]) unless cf_service[:entity][:plan_updateable]

  cf_binding = binding(app_guid, service_guid)

  # get the plan, throws 422 if the plan could not be found
  plan_guid = plan_guid(service_guid, plan_entity[:id])
  cf_instance = put("/v2/service_instances/#{cf_binding[:entity][:service_instance_guid]}",
                    body: { service_plan_guid: plan_guid }).body
  to_nucleus_installed_service(cf_binding, cf_service, cf_instance)
end
installed_service(application_name_or_id, service_id_or_name) click to toggle source

@see Stub#installed_service

# File lib/nucleus/adapters/v1/cloud_foundry_v2/services.rb, line 44
def installed_service(application_name_or_id, service_id_or_name)
  app_guid = app_guid(application_name_or_id)
  service_guid = service_guid(service_id_or_name)
  cf_binding = binding(app_guid, service_guid)
  # make sure there is a binding
  raise Errors::AdapterResourceNotFoundError,
        "No such service '#{service_id_or_name}' for application '#{application_name_or_id}'" unless cf_binding
  to_nucleus_installed_service(cf_binding)
end
installed_services(application_name_or_id) click to toggle source

@see Stub#installed_services

# File lib/nucleus/adapters/v1/cloud_foundry_v2/services.rb, line 36
def installed_services(application_name_or_id)
  app_guid = app_guid(application_name_or_id)
  get("/v2/apps/#{app_guid}/service_bindings?inline-relations-depth=1").body[:resources].collect do |binding|
    to_nucleus_installed_service(binding)
  end
end
remove_service(application_name_or_id, service_id) click to toggle source

@see Stub#remove_service

# File lib/nucleus/adapters/v1/cloud_foundry_v2/services.rb, line 93
def remove_service(application_name_or_id, service_id)
  app_guid = app_guid(application_name_or_id)
  service_guid = service_guid(service_id)
  # sadly we can't resolve the binding and instance from the service_id with ease
  # we therefore setup a chain to resolve the binding and instance from the active pla
  binding = binding(app_guid, service_guid)

  # now remove the binding from the application
  delete("/v2/apps/#{app_guid}/service_bindings/#{binding[:metadata][:guid]}", expects: [201])
  # and finally delete the service instance
  delete("/v2/service_instances/#{binding[:entity][:service_instance_guid]}")
end
service(service_id_or_name) click to toggle source

@see Stub#service

# File lib/nucleus/adapters/v1/cloud_foundry_v2/services.rb, line 17
def service(service_id_or_name)
  service_guid = service_guid(service_id_or_name)
  to_nucleus_service(get("/v2/services/#{service_guid}?inline-relations-depth=1").body)
end
service_plan(service_id_or_name, plan_id) click to toggle source

@see Stub#service_plan

# File lib/nucleus/adapters/v1/cloud_foundry_v2/services.rb, line 29
def service_plan(service_id_or_name, plan_id)
  service_guid = service_guid(service_id_or_name)
  plan_guid = plan_guid(service_guid, plan_id, Errors::AdapterResourceNotFoundError)
  to_nucleus_plan(get("/v2/service_plans/#{plan_guid}").body)
end
service_plans(service_id_or_name) click to toggle source

@see Stub#service_plans

# File lib/nucleus/adapters/v1/cloud_foundry_v2/services.rb, line 23
def service_plans(service_id_or_name)
  service_guid = service_guid(service_id_or_name)
  load_plans(service_guid).collect { |plan| to_nucleus_plan(plan) }
end
services() click to toggle source

@see Stub#services

# File lib/nucleus/adapters/v1/cloud_foundry_v2/services.rb, line 8
def services
  get('/v2/services?inline-relations-depth=1').body[:resources].collect do |service|
    # show only services that are both, active and bindable
    next unless service[:entity][:active] && service[:entity][:bindable]
    to_nucleus_service(service)
  end.compact
end

Private Instance Methods

apply_metadata(apply_to, cf_object) click to toggle source
# File lib/nucleus/adapters/v1/cloud_foundry_v2/services.rb, line 276
def apply_metadata(apply_to, cf_object)
  apply_to[:id] = cf_object[:metadata][:guid]
  apply_to[:created_at] = cf_object[:metadata][:created_at]
  apply_to[:updated_at] = cf_object[:metadata][:updated_at]
  apply_to
end
binding(app_guid, service_id) click to toggle source
# File lib/nucleus/adapters/v1/cloud_foundry_v2/services.rb, line 146
def binding(app_guid, service_id)
  service_plans = get("/v2/services/#{service_id}/service_plans").body[:resources]
  service_plan_ids = service_plans.collect { |plan| plan[:metadata][:guid] }
  app_bindings = get("/v2/apps/#{app_guid}/service_bindings?inline-relations-depth=1").body[:resources]
  # the plan must be bound to the app via an instance
  app_bindings.find do |binding|
    service_plan_ids.include?(binding[:entity][:service_instance][:entity][:service_plan_guid])
  end
end
binding_properties(binding) click to toggle source
# File lib/nucleus/adapters/v1/cloud_foundry_v2/services.rb, line 269
def binding_properties(binding)
  # in the credentials there are information such as: hostname, username, password, license keys, ...
  binding[:entity][:credentials].collect do |key, value|
    { key: key, value: value, description: nil }
  end
end
free_plan?(service_id, plans = nil) click to toggle source

Memoize this detection. The information is not critical, but takes some time to evaluate. Values are not expected to change often.

# File lib/nucleus/adapters/v1/cloud_foundry_v2/services.rb, line 174
def free_plan?(service_id, plans = nil)
  @free_plans ||= {}
  return @free_plans[service_id] if @free_plans.key?(service_id)
  plans = load_plans(service_id) unless plans
  @free_plans[service_id] = plans.any? { |plan| plan[:entity][:free] }
  @free_plans[service_id]
end
load_allowed_service(service_entity, service_guid) click to toggle source
# File lib/nucleus/adapters/v1/cloud_foundry_v2/services.rb, line 108
def load_allowed_service(service_entity, service_guid)
  begin
    cf_service = get("/v2/services/#{service_guid}").body
  rescue Errors::AdapterResourceNotFoundError
    # convert to semantic error with the service being a body, not a path entity
    raise Errors::SemanticAdapterRequestError,
          "Invalid service: Could not find service with the ID '#{service_entity[:id]}'"
  end

  # must be active and bindable?
  # currently we focus only on bindable services
  fail_with(:service_not_bindable, [service_entity[:id]]) unless cf_service[:entity][:bindable]
  # service must be active, otherwise we can't create the instance
  fail_with(:service_not_active, [service_entity[:id]]) unless cf_service[:entity][:active]
  # service seems to be valid, return
  cf_service
end
load_instance(cf_binding) click to toggle source
# File lib/nucleus/adapters/v1/cloud_foundry_v2/services.rb, line 255
def load_instance(cf_binding)
  if cf_binding[:entity].key?(:service_instance)
    # use if nested property is available
    cf_binding[:entity][:service_instance]
  else
    get("/v2/service_instances/#{cf_binding[:entity][:service_instance_guid]}").body
  end
end
load_plans(service_id) click to toggle source
# File lib/nucleus/adapters/v1/cloud_foundry_v2/services.rb, line 167
def load_plans(service_id)
  get("/v2/services/#{service_id}/service_plans").body[:resources]
end
load_service(cf_instance) click to toggle source
# File lib/nucleus/adapters/v1/cloud_foundry_v2/services.rb, line 264
def load_service(cf_instance)
  get("/v2/service_plans/#{cf_instance[:entity][:service_plan_guid]}"\
    '?inline-relations-depth=1').body[:entity][:service]
end
plan_guid(service_id, plan_name_or_id, error_class = Errors::SemanticAdapterRequestError) click to toggle source
# File lib/nucleus/adapters/v1/cloud_foundry_v2/services.rb, line 156
def plan_guid(service_id, plan_name_or_id, error_class = Errors::SemanticAdapterRequestError)
  return plan_name_or_id if guid?(plan_name_or_id)
  # list all plans for the service
  plans = get("/v2/services/#{service_id}/service_plans").body[:resources]
  # find a match and use the plan's guid
  plan_match = plans.find { |plan| plan[:entity][:name] == plan_name_or_id }
  raise error_class,
        "Invalid plan: No such plan '#{plan_name_or_id}' for service '#{service_id}'" unless plan_match
  plan_match[:metadata][:guid]
end
remove_all_services(app_guid) click to toggle source
# File lib/nucleus/adapters/v1/cloud_foundry_v2/services.rb, line 126
def remove_all_services(app_guid)
  get("/v2/apps/#{app_guid}/service_bindings").body[:resources].collect do |binding|
    # remove the binding from the application
    delete("/v2/apps/#{app_guid}/service_bindings/#{binding[:metadata][:guid]}", expects: [201])
    # and delete the service instance to prevent orphans
    delete("/v2/service_instances/#{binding[:entity][:service_instance_guid]}")
  end
end
service_guid(service_id_or_name, error_class = Errors::AdapterResourceNotFoundError) click to toggle source
# File lib/nucleus/adapters/v1/cloud_foundry_v2/services.rb, line 135
def service_guid(service_id_or_name, error_class = Errors::AdapterResourceNotFoundError)
  return service_id_or_name if guid?(service_id_or_name)
  # list all available services
  services = get('/v2/services').body[:resources]
  # find a match and use the service's guid
  service_match = services.find { |service| service[:entity][:label] == service_id_or_name }
  raise error_class,
        "Invalid service: Could not find service with name '#{service_id_or_name}'" unless service_match
  service_match[:metadata][:guid]
end
to_nucleus_installed_service(cf_binding, cf_service = nil, cf_instance = nil) click to toggle source

Show the installed service. Therefore we need:
<ul> <li>the classic service for the basic information (version, name, …)</li> <li>the binding for the properties and metadata (id, timestamps, …)</li> <li>the bound instance for the active plan and web_url</li> </ul>

# File lib/nucleus/adapters/v1/cloud_foundry_v2/services.rb, line 235
def to_nucleus_installed_service(cf_binding, cf_service = nil, cf_instance = nil)
  # load if not provided
  cf_instance = load_instance(cf_binding) unless cf_instance
  cf_service = load_service(cf_instance) unless cf_service
  # load if not provided
  unless cf_service
    cf_service = get("/v2/service_plans/#{cf_instance[:entity][:service_plan_guid]}"\
      '?inline-relations-depth=1').body[:entity][:service]
  end

  # active_plan, web_url, properties
  service = to_nucleus_service(cf_service)
  # use the metadata of the binding, is more future proof than instance metadata
  apply_metadata(service, cf_binding)
  service[:active_plan] = cf_instance[:entity][:service_plan_guid]
  service[:web_url] = cf_instance[:entity][:dashboard_url]
  service[:properties] = binding_properties(cf_binding)
  service
end
to_nucleus_plan(cf_plan) click to toggle source
# File lib/nucleus/adapters/v1/cloud_foundry_v2/services.rb, line 182
def to_nucleus_plan(cf_plan)
  plan = cf_plan[:entity]
  plan[:id] = cf_plan[:metadata][:guid]
  plan[:created_at] = cf_plan[:metadata][:created_at]
  plan[:updated_at] = cf_plan[:metadata][:updated_at]
  plan[:costs] = []
  plan

  # TODO: determine prices for CF services
  # we know how IBM handles the costs, but can't determine the country
  # we know how Pivotal IO handles the costs
  # but what do the others???

  # extra = Oj.load(plan[:extra])
  # if plan[:free]
  #   plan[:costs] = { period: '', per_instance: false, price: { amount: 0.00, currency: nil} }
  # elsif endpoint_url.include?('pivotal.io')
  #   # show prices for Pivotal Web Services
  #   # see for an explanation: http://docs.pivotal.io/pivotalcf/services/catalog-metadata.html
  #   plan[:costs] = extra[:costs].collect do |cost|
  #     prices = cost[:amount].collect { |currency, amount| { currency: currency, amount: amount } }
  #     { per_instance: false, period: cost[:unit], price: prices }
  #   end
  # elsif endpoint_url.include?('bluemix.net')
  #   # show prices for IBM Bluemix
  # else
  #   # fallback, unknown CF system
  # end
end
to_nucleus_service(cf_service) click to toggle source
# File lib/nucleus/adapters/v1/cloud_foundry_v2/services.rb, line 212
def to_nucleus_service(cf_service)
  service = cf_service[:entity]
  service = apply_metadata(service, cf_service)
  service[:name] = service.delete(:label)
  service[:release] = service.delete(:version)
  service[:free_plan] = if cf_service[:entity].key?(:service_plans)
                          # use preloaded plans if available
                          free_plan?(service[:id], cf_service[:entity][:service_plans])
                        else
                          free_plan?(service[:id])
                        end
  # CF does not have service dependencies
  service[:required_services] = service.delete(:requires)
  # description and documentation_url should already be set
  service
end