module Nucleus::Adapters::V1::Heroku::Services

Public Instance Methods

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

@see Stub#add_service

# File lib/nucleus/adapters/v1/heroku/services.rb, line 41
def add_service(application_id, service_entity, plan_entity)
  begin
    # make sure plan belongs to this service, throws 404 if no such plan
    # the service plan itself requires the name, e.g. 'sandbox' or the UUID
    service_plan(service_entity[:id], plan_entity[:id])
  rescue Errors::AdapterResourceNotFoundError => e
    # convert to 422
    raise Errors::SemanticAdapterRequestError, e.message
  end
  # the plan to choose requires the UUID of the plan OR the combination of both names
  plan_id = service_plan_identifier(service_entity[:id], plan_entity[:id])
  created = post("/apps/#{application_id}/addons", body: { plan: plan_id }).body
  to_nucleus_installed_service(created)
end
change_service(application_id, service_id, plan_entity) click to toggle source

@see Stub#change_service

# File lib/nucleus/adapters/v1/heroku/services.rb, line 57
def change_service(application_id, service_id, plan_entity)
  # make sure service is bound to the application
  assignment_id = raw_installed_service(application_id, service_id)[:id]
  begin
    # make sure plan belongs to this service, throws 404 if no such plan
    # the service plan itself requires the name, e.g. 'sandbox' or the UUID
    service_plan(service_id, plan_entity[:id])
  rescue Errors::AdapterResourceNotFoundError => e
    # convert to 422
    raise Errors::SemanticAdapterRequestError, e.message
  end
  # the plan to choose requires the UUID of the plan OR the combination of both names
  plan_id = service_plan_identifier(service_id, plan_id)
  updated = patch("/apps/#{application_id}/addons/#{assignment_id}", body: { plan: plan_id }).body
  to_nucleus_installed_service(updated)
end
installed_service(application_id, service_id) click to toggle source

@see Stub#installed_service

# File lib/nucleus/adapters/v1/heroku/services.rb, line 35
def installed_service(application_id, service_id)
  assigned_service = raw_installed_service(application_id, service_id)
  to_nucleus_installed_service(assigned_service)
end
installed_services(application_id) click to toggle source

@see Stub#installed_services

# File lib/nucleus/adapters/v1/heroku/services.rb, line 30
def installed_services(application_id)
  get("/apps/#{application_id}/addons").body.collect { |service| to_nucleus_installed_service(service) }
end
remove_service(application_id, service_id) click to toggle source

@see Stub#remove_service

# File lib/nucleus/adapters/v1/heroku/services.rb, line 75
def remove_service(application_id, service_id)
  # make sure service is bound to the application
  assignment_id = raw_installed_service(application_id, service_id)[:id]
  delete("/apps/#{application_id}/addons/#{assignment_id}")
end
service(service_id) click to toggle source

@see Stub#service

# File lib/nucleus/adapters/v1/heroku/services.rb, line 12
def service(service_id)
  to_nucleus_service(get("/addon-services/#{service_id}").body)
end
service_plan(service_id, plan_id) click to toggle source

@see Stub#service_plan

# File lib/nucleus/adapters/v1/heroku/services.rb, line 25
def service_plan(service_id, plan_id)
  to_nucleus_plan(get("/addon-services/#{service_id}/plans/#{plan_id}").body)
end
service_plans(service_id) click to toggle source

@see Stub#service_plans

# File lib/nucleus/adapters/v1/heroku/services.rb, line 17
def service_plans(service_id)
  load_plans(service_id).collect { |plan| to_nucleus_plan(plan) }.sort_by do |plan|
    # only compare the first cost, covers most cases and sorting for all costs would be far too complex
    plan[:costs][0][:price].first[:amount].to_f
  end
end
services() click to toggle source

@see Stub#services

# File lib/nucleus/adapters/v1/heroku/services.rb, line 7
def services
  get('/addon-services').body.collect { |service| to_nucleus_service(service) }
end

Private Instance Methods

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/heroku/services.rb, line 157
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[:price][:cents] == 0 }
  @free_plans[service_id]
end
load_plans(service_id) click to toggle source
# File lib/nucleus/adapters/v1/heroku/services.rb, line 150
def load_plans(service_id)
  get("/addon-services/#{service_id}/plans").body
end
raw_installed_service(application_id, service_id) click to toggle source
# File lib/nucleus/adapters/v1/heroku/services.rb, line 96
def raw_installed_service(application_id, service_id)
  # here we probably receive the ID of the service, not the service assignment ID itself
  installed = get("/apps/#{application_id}/addons/#{service_id}", expects: [200, 404])
  if installed.status == 404
    assignment_id = service_assignment_id(application_id, service_id)
    raise Errors::AdapterResourceNotFoundError,
          "Service #{service_id} is not assigned to application #{application_id}" unless assignment_id
    return get("/apps/#{application_id}/addons/#{assignment_id}").body
  end
  installed.body
end
service_assignment_id(application_id, service_id) click to toggle source
# File lib/nucleus/adapters/v1/heroku/services.rb, line 108
def service_assignment_id(application_id, service_id)
  all_services = get("/apps/#{application_id}/addons").body
  match = all_services.find do |addon|
    addon[:addon_service][:id] == service_id || addon[:addon_service][:name] == service_id
  end
  return match[:id] if match
  nil
end
service_plan_identifier(service_id, plan_id) click to toggle source
# File lib/nucleus/adapters/v1/heroku/services.rb, line 83
def service_plan_identifier(service_id, plan_id)
  # process plan id_or_name to build the unique identifier
  # a) is a UUID
  return plan_id if Regexp::UUID_PATTERN =~ plan_id
  # b) is valid identifier, contains ':'
  return plan_id if /^[-\w]+:[-\w]+$/i =~ plan_id
  # c) fetch id for name
  return "#{service_id}:#{plan_id}" unless Regexp::UUID_PATTERN =~ service_id
  # arriving here, service_id is UUID but plan_id is the name --> DOH!
  # we return the plan_id and the request will presumably fail
  plan_id
end
to_nucleus_installed_service(installed_service) click to toggle source
# File lib/nucleus/adapters/v1/heroku/services.rb, line 126
def to_nucleus_installed_service(installed_service)
  service = service(installed_service[:addon_service][:id])
  # get all variables and reject all that do not belong to the addon
  unless installed_service[:config_vars].nil? && installed_service[:config_vars].empty?
    vars = get("/apps/#{installed_service[:app][:id]}/config-vars").body
    # ignore all vars that do not belong to the service
    vars = vars.delete_if { |k| !installed_service[:config_vars].include?(k.to_s) }
    # format to desired format
    vars = vars.collect { |k, v| { key: k, value: v, description: nil } }
  end
  service[:properties] = vars ? vars : []
  service[:active_plan] = installed_service[:plan][:id]
  service[:web_url] = installed_service[:web_url]
  service
end
to_nucleus_plan(plan) click to toggle source
# File lib/nucleus/adapters/v1/heroku/services.rb, line 142
def to_nucleus_plan(plan)
  # TODO: extract payment period to enum
  plan[:costs] = [{ price: [amount: plan[:price][:cents] / 100.0, currency: 'USD'],
                   period: plan[:price][:unit], per_instance: false }]
  plan[:free] = plan[:price][:cents] == 0
  plan
end
to_nucleus_service(service) click to toggle source
# File lib/nucleus/adapters/v1/heroku/services.rb, line 117
def to_nucleus_service(service)
  service[:description] = service.delete(:human_name)
  service[:release] = service.delete(:state)
  service[:required_services] = []
  service[:free_plan] = free_plan?(service[:id])
  service[:documentation_url] = "https://addons.heroku.com/#{service[:name]}"
  service
end